Генератор имеет свои плагины. Как правило, это готовый код, который нужно только использовать. Плагины доступны только для игры GTA San Andreas. Чтобы использовать их, нужно подключить пространство имён:
using GTA.SA.Plugins;
На текущий момент, доступны следующие плагины:
Этот плагин позволит использовать проигрыватель аудио. Его цель в основном служит для диалогов. Но и для других целей его тоже можно использовать.
Плагин требует: 4 глобальных переменных, инициализацию только в потоках ( не в миссиях и не во внешних скриптах ). Пример использования плагина:
using GTA.SA.Plugins;
// --- --- ---
private static AudioPlayer audioPlayer; // здесь будем хранить ссылку на аудио-плеер
[Thread]
public void MAIN() {
create_thread( AUDIO ); // запускаем поток с инициализацией плеера
create_thread( TEST ); // использовать плагин нужно только после инициализации!
end_thread();
}
[Thread]
public void AUDIO() {
// в скобках перечислены индексы глобальных переменных:
audioPlayer = new AudioPlayer( 2000, 2001, 2002, 2003 );
}
[Thread]
public void TEST() {
// загружаем аудио-файлы. 1: с какого индекса начинать, 5: количество файлов
audioPlayer.Load( 1, 5 ); // индекс должен быть больше нуля, а количество: меньше 26
jf( 0, audioPlayer.IsReady ); // ожидаем загрузки всех файлов
for( int i = 0; i < audioPlayer.Count; i++ ) {
audioPlayer.PlayNext(); // воспроизводим следующий аудио-файл
wait( audioPlayer.Lenght ); // ожидаем, пока звук закончит воспроизведение
}
audioPlayer.Unload(); // выгружаем все аудио-файлы
end_thread();
}
Если проверить исходный код, то становится понятно, что все аудио-файлы должны находится в папке "Игра\sound". Расширение файла должно быть "mp3". А названия должны быть только числовыми. Метод загрузки получает первый индекс и количество файлов. Загрузка происходит в цикле, и в нашем примере будут загружены файлы с именами: 1, 2, 3, 4 и 5. Метод "PlayNext" сначала начнёт воспроизводить трек "1". При повторном вызове: "2", далее "3" и т.п. Всего можно загрузить не больше 25 звуков за раз. Если нужно больше, то загружать надо частями.
Вот так будет выглядеть наш код, прекрасно и кошмарно для новичков-скриптеров:
DEFINE OBJECTS 0
DEFINE MISSIONS 0
DEFINE EXTERNAL_SCRIPTS 0 // Use -1 in order not to compile AAA script
DEFINE UNKNOWN_EMPTY_SEGMENT 0
DEFINE UNKNOWN_THREADS_MEMORY 2048
//------------- MAIN ---------------
:MAIN
03A4: name_thread 'MAIN'
0180: set_on_mission_flag_to $409 // Note: your missions have to use the variable defined here
0004: $14 = 250 // $ = ? (int)
0111: set_wasted_busted_check 0
004F: create_thread @AUDIO
004F: create_thread @TEST
004E: end_thread
//------------- AUDIO ---------------
:AUDIO
03A4: name_thread 'AUDIO'
0006: 0@ = 0 // @ = ? (int)
0006: 31@ = 0 // @ = ? (int)
:AUDIO_AUTO_LABEL_0
0001: wait $14 ms
00D6: if
8038: not $2000 == 0 // $ == ? (int)
004D: jump_if_false @AUDIO_AUTO_LABEL_0
:AUDIO_LOOP_0
0001: wait 0 ms
0871: init_jump_table $2000 total_jumps 4 default_jump 0 @AUDIO_SWITCH_0_CASE_END jumps 0 @AUDIO_SWITCH_0_CASE_0 1 @AUDIO_SWITCH_0_CASE_1 2 @AUDIO_SWITCH_0_CASE_2 3 @AUDIO_SWITCH_0_CASE_3 -1 @AUDIO_SWITCH_0_CASE_END -1 @AUDIO_SWITCH_0_CASE_END -1 @AUDIO_SWITCH_0_CASE_END
:AUDIO_SWITCH_0_CASE_0
0002: jump @AUDIO_SWITCH_0_CASE_END
:AUDIO_SWITCH_0_CASE_1
int 31@
for 31@ = $2001 to $2002 step 1
0AD3: 1@v = format "sound\%d.mp3" 31@
00D6: if
0AAB: file_exists 1@v
then
00D6: if
8AAC: not 5@(31@,25i) = load_audiostream 1@v
then
0006: 5@(31@,25i) = -1 // @ = ? (int)
end
else
0006: 5@(31@,25i) = -1 // @ = ? (int)
end
end
0004: $2000 = 2 // $ = ? (int)
0002: jump @AUDIO_SWITCH_0_CASE_END
:AUDIO_SWITCH_0_CASE_2
008B: 0@ = $2001 // @ = $ (int)
000E: 0@ -= 1 // @ -= ? (int)
0004: $2003 = 0 // $ = ? (int)
00D6: if
8039: not 0@ == -1 // @ == ? (int)
then
0AAD: set_mp3 5@(0@,25i) perform_action 0
end
00D6: if
8039: not 5@($2001,25i) == -1 // @ == ? (int)
then
0AAD: set_mp3 5@($2001,25i) perform_action 1
0AAF: $2003 = get_mp3_length 5@($2001,25i)
end
0002: jump @AUDIO_SWITCH_0_CASE_END
:AUDIO_SWITCH_0_CASE_3
for 31@ = 0 to 25
00D6: if
8039: not 5@(31@,25i) == -1 // @ == ? (int)
then
0AAD: set_mp3 5@(31@,25i) perform_action 0
0AAE: release_mp3 5@(31@,25i)
0006: 5@(31@,25i) = -1 // @ = ? (int)
end
end
0004: $2002 = -1 // $ = ? (int)
0004: $2003 = 0 // $ = ? (int)
0004: $2000 = 0 // $ = ? (int)
0002: jump @AUDIO_SWITCH_0_CASE_END
:AUDIO_SWITCH_0_CASE_END
00D6: if
0038: $2000 == 0 // $ == ? (int)
then
0002: jump @AUDIO
end
0002: jump @AUDIO_LOOP_0
//------------- TEST ---------------
:TEST
03A4: name_thread 'TEST'
0004: $2001 = 0 // $ = ? (int)
0004: $2002 = 6 // $ = ? (int)
0004: $2000 = 1 // $ = ? (int)
:TEST_AUTO_LABEL_0
0001: wait 0 ms
00D6: if
0038: $2000 == 2 // $ == ? (int)
004D: jump_if_false @TEST_AUTO_LABEL_0
0008: $2001 += 1 // $ += ? (int)
0004: $2000 = 3 // $ = ? (int)
0001: wait $2003 ms
0008: $2001 += 1 // $ += ? (int)
0004: $2000 = 3 // $ = ? (int)
0001: wait $2003 ms
0008: $2001 += 1 // $ += ? (int)
0004: $2000 = 3 // $ = ? (int)
0001: wait $2003 ms
0008: $2001 += 1 // $ += ? (int)
0004: $2000 = 3 // $ = ? (int)
0001: wait $2003 ms
0008: $2001 += 1 // $ += ? (int)
0004: $2000 = 3 // $ = ? (int)
0001: wait $2003 ms
0004: $2000 = 4 // $ = ? (int)
004E: end_thread
В примере поток "AUDIO" берёт на себя всю логику. Нам остаётся только указывать что и когда загружать/воспроизводить. Изменяя ту или иную переменную, мы даём потоку знать что делать дальше. Вот такой нехитрый способ упростить себе жизнь с аудио-файлами.
Плагины имеют несколько классов для работы со стартерами. Ниже приведён их список с описаниями:
Этот вариант наиболее прост в освоении и реализации, так как не имеет никаких настроек. Конструктор принимает 7 обязательных параметров, остальные можно добавить когда нужно. Обязательно нужно указывать ID иконки радара, позицию маркера и два индекса глобальных переменных. После этого идёт перечень миссий, которые будут запускаться в стартере. Нужно указать минимум одну миссию!
Стартер имеет два свойства и один метод. "TotalMission" возвращает количество миссий, с которыми работает стартер. "MissionPassed" возвращает переменную, которая хранит количество пройденных миссий ( иногда в других потоках нужно проверить пройден ли какой-то этап ). Есть ещё метод "Recreate". Он создаёт новый поток стартера ( использовать нужно в миссиях, в коде провала и победы ). Принимает только один параметр, который указывает нужно ли увеличивать счётчик миссий.
Ниже приведён код использования простого стартера:
using GTA.SA.Plugins;
// --- --- ---
[Thread]
public void MAIN() {
create_thread( STARTER );
end_thread();
}
private static StarterEasy CJ_STARTER; // ссылка на стартер
[Thread]
public void STARTER() {
// создаём новый стартер
CJ_STARTER = new StarterEasy( RadarIconID.CJ, 0.0, 1.0, 2.0, 2004, 2005, MISS1, MISS2 );
}
[Mission]
public void MISS1() {
Mission.OnPassed = delegate { CJ_STARTER.Recreate( true ); };
Mission.OnFailed = delegate { CJ_STARTER.Recreate(); };
}
[Mission]
public void MISS2() {
Mission.OnPassed = delegate { CJ_STARTER.Recreate( true ); };
Mission.OnFailed = delegate { CJ_STARTER.Recreate(); };
}
В итоге мы получим такой код:
DEFINE OBJECTS 0 DEFINE MISSIONS 2 DEFINE MISSION 0 AT @MISS1 // 0 DEFINE MISSION 1 AT @MISS2 // 1 DEFINE EXTERNAL_SCRIPTS 0 // Use -1 in order not to compile AAA script DEFINE UNKNOWN_EMPTY_SEGMENT 0 DEFINE UNKNOWN_THREADS_MEMORY 2048 //------------- MAIN --------------- :MAIN 03A4: name_thread 'MAIN' 0180: set_on_mission_flag_to $409 // Note: your missions have to use the variable defined here 0004: $14 = 250 // $ = ? (int) 0111: set_wasted_busted_check 0 004F: create_thread @STARTER 004E: end_thread //------------- STARTER --------------- :STARTER 03A4: name_thread 'STARTER' 00D6: if or 001A: 0 > $2005 // ? > $ (int) 0028: $2005 >= 2 // $ >= ? (int) then 0002: jump @STARTER_END end 0111: set_wasted_busted_check 0 02A7: $2004 = create_icon_marker_and_sphere 15 at 0.0 1.0 2.0 :STARTER_AUTO_LABEL_0 0001: wait $14 ms 00D6: if 0038: $409 == 0 // $ == ? (int) 004D: jump_if_false @STARTER_AUTO_LABEL_0 00D6: if 0256: player $2 defined 004D: jump_if_false @STARTER_AUTO_LABEL_0 00D6: if 03EE: player $2 controllable 004D: jump_if_false @STARTER_AUTO_LABEL_0 00D6: if 0102: actor $3 in_sphere 0.0 1.0 2.0 radius 1.5 1.5 1.5 sphere 0 stopped_on_foot 004D: jump_if_false @STARTER_AUTO_LABEL_0 00D6: if 816B: not fading 004D: jump_if_false @STARTER_AUTO_LABEL_0 00D6: if 8118: not actor $3 dead 004D: jump_if_false @STARTER_AUTO_LABEL_0 00D6: if 8741: not actor $3 busted 004D: jump_if_false @STARTER_AUTO_LABEL_0 01F7: set_player $2 ignored_by_cops 1 03BF: set_player $2 ignored_by_everyone 1 02AB: set_actor $3 immunities BP 1 FP 1 EP 1 CP 1 MP 1 01B4: set_player $2 can_move 0 0164: disable_marker $2004 016A: fade 0 time 1000 00D6: if 0038: $2005 == 0 // $ == ? (int) then 0050: gosub @STARTER_0 end 00D6: if 0038: $2005 == 1 // $ == ? (int) then 0050: gosub @STARTER_1 end :STARTER_END 004E: end_thread :STARTER_0 0417: start_mission 0 0051: return :STARTER_1 0417: start_mission 1 0051: return //------------- Mission: MISS1 (0) --------------- :MISS1 03A4: name_thread 'MISS1' 0050: gosub @MISS1_START 00D6: if 0112: wasted_or_busted // mission only 004D: jump_if_false @MISS1_END 0050: gosub @MISS1_FAILED :MISS1_END 0004: $409 = 0 // $ = ? (int) 00D8: mission_cleanup 004E: end_thread :MISS1_START 0317: increment_mission_attempts 0004: $409 = 1 // $ = ? (int) :MISS1_PASSED 0050: gosub @MISS1_CLEAR 0008: $2005 += 1 // $ += ? (int) 004F: create_thread @STARTER 0051: return :MISS1_FAILED 0050: gosub @MISS1_CLEAR 004F: create_thread @STARTER 0051: return :MISS1_CLEAR 0051: return //------------- Mission: MISS2 (1) --------------- :MISS2 03A4: name_thread 'MISS2' 0050: gosub @MISS2_START 00D6: if 0112: wasted_or_busted // mission only 004D: jump_if_false @MISS2_END 0050: gosub @MISS2_FAILED :MISS2_END 0004: $409 = 0 // $ = ? (int) 00D8: mission_cleanup 004E: end_thread :MISS2_START 0317: increment_mission_attempts 0004: $409 = 1 // $ = ? (int) :MISS2_PASSED 0050: gosub @MISS2_CLEAR 0008: $2005 += 1 // $ += ? (int) 004F: create_thread @STARTER 0051: return :MISS2_FAILED 0050: gosub @MISS2_CLEAR 004F: create_thread @STARTER 0051: return :MISS2_CLEAR 0051: return
Когда идёт запуск миссий, игрок получит все иммунитеты, отключается контроль и его будут игнорировать все. В процессе миссии эти возможности нужно отключать вручную.
Этот тип стартера работает также, как и простой. Отличие только в том, что мы можем дополнительно перемещать стартер в другую позицию и изменять иконку радара, не говоря уже о том, что можно получить координаты стартера. Параметры конструктора отличаются и требуется сразу 7 индексов глобальных переменных. Давайте рассмотрим пример использования такого стартера:
using GTA.SA.Plugins;
// --- --- ---
[Thread]
public void MAIN() {
create_thread( STARTER );
end_thread();
}
private static StarterMiddle CJ_STARTER;
[Thread]
public void STARTER() {
CJ_STARTER = new StarterMiddle( RadarIconID.CJ, 0.0, 1.0, 2.0, 2004, 2005, 2006, 2007, 2008, 2009, 2010, MISS1, MISS2 );
}
[Mission]
public void MISS1() {
Mission.OnPassed = delegate {
CJ_STARTER.Move( 1440.0, 24.0, 13.0 ); // перемещение стартера
CJ_STARTER.ChangeIcon( RadarIconID.SWEET ); // смена иконки стартера
CJ_STARTER.Recreate( true );
};
Mission.OnFailed = delegate { CJ_STARTER.Recreate(); };
}
[Mission]
public void MISS2() {
Mission.OnPassed = delegate { CJ_STARTER.Recreate( true ); };
Mission.OnFailed = delegate { CJ_STARTER.Recreate(); };
}
Изменять позицию и иконку надо перед тем, как мы создадим стартер заново! Этот код будет иметь такой вид на выходе:
DEFINE OBJECTS 0 DEFINE MISSIONS 2 DEFINE MISSION 0 AT @MISS1 // 0 DEFINE MISSION 1 AT @MISS2 // 1 DEFINE EXTERNAL_SCRIPTS 0 // Use -1 in order not to compile AAA script DEFINE UNKNOWN_EMPTY_SEGMENT 0 DEFINE UNKNOWN_THREADS_MEMORY 2048 //------------- MAIN --------------- :MAIN 03A4: name_thread 'MAIN' 0180: set_on_mission_flag_to $409 // Note: your missions have to use the variable defined here 0004: $14 = 250 // $ = ? (int) 0111: set_wasted_busted_check 0 004F: create_thread @STARTER 004E: end_thread //------------- STARTER --------------- :STARTER 03A4: name_thread 'STARTER' 00D6: if or 001A: 0 > $2005 // ? > $ (int) 0028: $2005 >= 2 // $ >= ? (int) then 0002: jump @STARTER_END end 0111: set_wasted_busted_check 0 00D6: if 0038: $2010 == 0 // $ == ? (int) then 0004: $2006 = 15 // $ = ? (int) 0005: $2007 = 0.0 // $ = ? (float) 0005: $2008 = 1.0 // $ = ? (float) 0005: $2009 = 2.0 // $ = ? (float) 0004: $2010 = 1 // $ = ? (int) end 02A7: $2004 = create_icon_marker_and_sphere $2006 at $2007 $2008 $2009 :STARTER_AUTO_LABEL_0 0001: wait $14 ms 00D6: if 0038: $409 == 0 // $ == ? (int) 004D: jump_if_false @STARTER_AUTO_LABEL_0 00D6: if 0256: player $2 defined 004D: jump_if_false @STARTER_AUTO_LABEL_0 00D6: if 03EE: player $2 controllable 004D: jump_if_false @STARTER_AUTO_LABEL_0 00D6: if 0102: actor $3 in_sphere $2007 $2008 $2009 radius 1.5 1.5 1.5 sphere 0 stopped_on_foot 004D: jump_if_false @STARTER_AUTO_LABEL_0 00D6: if 816B: not fading 004D: jump_if_false @STARTER_AUTO_LABEL_0 00D6: if 8118: not actor $3 dead 004D: jump_if_false @STARTER_AUTO_LABEL_0 00D6: if 8741: not actor $3 busted 004D: jump_if_false @STARTER_AUTO_LABEL_0 01F7: set_player $2 ignored_by_cops 1 03BF: set_player $2 ignored_by_everyone 1 02AB: set_actor $3 immunities BP 1 FP 1 EP 1 CP 1 MP 1 01B4: set_player $2 can_move 0 0164: disable_marker $2004 016A: fade 0 time 1000 00D6: if 0038: $2005 == 0 // $ == ? (int) then 0050: gosub @STARTER_0 end 00D6: if 0038: $2005 == 1 // $ == ? (int) then 0050: gosub @STARTER_1 end :STARTER_END 004E: end_thread :STARTER_0 0417: start_mission 0 0051: return :STARTER_1 0417: start_mission 1 0051: return //------------- Mission: MISS1 (0) --------------- :MISS1 03A4: name_thread 'MISS1' 0050: gosub @MISS1_START 00D6: if 0112: wasted_or_busted // mission only 004D: jump_if_false @MISS1_END 0050: gosub @MISS1_FAILED :MISS1_END 0004: $409 = 0 // $ = ? (int) 00D8: mission_cleanup 004E: end_thread :MISS1_START 0317: increment_mission_attempts 0004: $409 = 1 // $ = ? (int) :MISS1_PASSED 0050: gosub @MISS1_CLEAR 0005: $2007 = 1440.0 // $ = ? (float) 0005: $2008 = 24.0 // $ = ? (float) 0005: $2009 = 13.0 // $ = ? (float) 0004: $2006 = 38 // $ = ? (int) 0008: $2005 += 1 // $ += ? (int) 004F: create_thread @STARTER 0051: return :MISS1_FAILED 0050: gosub @MISS1_CLEAR 004F: create_thread @STARTER 0051: return :MISS1_CLEAR 0051: return //------------- Mission: MISS2 (1) --------------- :MISS2 03A4: name_thread 'MISS2' 0050: gosub @MISS2_START 00D6: if 0112: wasted_or_busted // mission only 004D: jump_if_false @MISS2_END 0050: gosub @MISS2_FAILED :MISS2_END 0004: $409 = 0 // $ = ? (int) 00D8: mission_cleanup 004E: end_thread :MISS2_START 0317: increment_mission_attempts 0004: $409 = 1 // $ = ? (int) :MISS2_PASSED 0050: gosub @MISS2_CLEAR 0008: $2005 += 1 // $ += ? (int) 004F: create_thread @STARTER 0051: return :MISS2_FAILED 0050: gosub @MISS2_CLEAR 004F: create_thread @STARTER 0051: return :MISS2_CLEAR 0051: return
Первые два стартера не имеют возможности выводить названия миссии и могут запускаться только тогда, когда игрок не в машине. Для более точной настройки запуска каждой миссии, используется следующий тип стартера.
Этот тип стартера имеет немного другой принцип инициализации. Конструктор требует ещё 2 глобальные переменные, которые используются для радиуса сферы и хранения этапа миссии. Сами миссии пишутся в отдельном методе, а генерация кода происходит после использования метода "Write".
Миссии нужно добавлять отдельно, используя метод "AddMission". Туда нужно указать GXT-имя миссии, ссылку на миссию и, если нужно, указать дополнительные условия старта. Методы "SetStage" и "SetRadius" позволяют изменить этап миссии и радиус сферы соответсвенно. А функция "GetMissionGXTName" возвращает GXT-имя миссии, которое было указано в стратере.
Давайте рассмотрим пример реализации многофункционального стартера:
using GTA.SA.Plugins;
// --- --- ---
[Thread]
public void MAIN() {
create_thread( STARTER );
end_thread();
}
private static StarterHard CJ_STARTER; // сохраним ссылку на стартер
[Thread]
public void STARTER() {
// сначала инициализируем стартер
// далее добавив несколько миссий; в первой напишем ещё условий
// потом генерируем код, используя метод "Write"
CJ_STARTER = new StarterHard( RadarIconID.CJ, 144.0, -14.0, 13.4, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 );
CJ_STARTER.AddMission( "BASE_1", MISS1, PlayerActor.is_stopped(), PlayerActor.is_in_any_vehicle() );
CJ_STARTER.AddMission( "BASE_2", MISS2 );
CJ_STARTER.Write();
}
[Mission]
public void MISS1() {
Mission.OnPassed = delegate {
// вот так можно задать имя миссии в сейв-файле
// нужно, что миссия MISS1 была добавлена в стартер
set_latest_mission_passed( CJ_STARTER.GetMissionGXTName( MISS1 ) );
var stage = CJ_STARTER.Stage; // получаем некий этап
stage += 1; // изменяем этап
// изменяем данные этапа, хотя в этом контексте писать его не нужно: он изменён выше в коде
CJ_STARTER.SetStage( stage );
and( CJ_STARTER.Stage > 2, delegate {
CJ_STARTER.Move( 1440.0, 24.0, 13.0 );
CJ_STARTER.ChangeIcon( RadarIconID.SWEET );
} );
CJ_STARTER.SetRadius( 6.0 ); // указываем новый радиус сферы стартера
CJ_STARTER.Recreate( true );
};
Mission.OnFailed = delegate {
CJ_STARTER.Recreate();
};
}
[Mission]
public void MISS2() {
Mission.OnPassed = delegate {
CJ_STARTER.Recreate( true );
};
Mission.OnFailed = delegate {
CJ_STARTER.Recreate();
};
}
Этот небольшой код генерирует для Sanny Builder вот такой код:
DEFINE OBJECTS 0
DEFINE MISSIONS 2
DEFINE MISSION 0 AT @MISS1 // 0
DEFINE MISSION 1 AT @MISS2 // 1
DEFINE EXTERNAL_SCRIPTS 0 // Use -1 in order not to compile AAA script
DEFINE UNKNOWN_EMPTY_SEGMENT 0
DEFINE UNKNOWN_THREADS_MEMORY 2048
//------------- MAIN ---------------
:MAIN
03A4: name_thread 'MAIN'
0180: set_on_mission_flag_to $409 // Note: your missions have to use the variable defined here
0004: $14 = 250 // $ = ? (int)
0111: set_wasted_busted_check 0
004F: create_thread @STARTER
004E: end_thread
//------------- STARTER ---------------
:STARTER
03A4: name_thread 'STARTER'
00D6: if or
001A: 0 > $2005 // ? > $ (int)
0028: $2005 >= 2 // $ >= ? (int)
then
0002: jump @STARTER_END
end
0111: set_wasted_busted_check 0
00D6: if
0038: $2010 == 0 // $ == ? (int)
then
0004: $2006 = 15 // $ = ? (int)
0005: $2007 = 144.0 // $ = ? (float)
0005: $2008 = -14.0 // $ = ? (float)
0005: $2009 = 13.4 // $ = ? (float)
0005: $2012 = 1.5 // $ = ? (float)
0004: $2010 = 1 // $ = ? (int)
end
02A7: $2004 = create_icon_marker_and_sphere $2006 at $2007 $2008 $2009
:STARTER_CHECK
0001: wait $14 ms
00D6: if or
0038: $409 == 1 // $ == ? (int)
016B: fading
8256: not player $2 defined
then
0002: jump @STARTER_CHECK
end
00D6: if or
83EE: not player $2 controllable
0118: actor $3 dead
0741: actor $3 busted
then
0002: jump @STARTER_CHECK
end
00D6: if
00FE: actor $3 sphere 0 in_sphere $2007 $2008 $2009 radius $2012 $2012 $2012
004D: jump_if_false @STARTER_CHECK
00D6: if
0038: $2005 == 0 // $ == ? (int)
then
00D6: if
02A0: actor $3 stopped
004D: jump_if_false @STARTER_CHECK
00D6: if
00DF: actor $3 driving
004D: jump_if_false @STARTER_CHECK
0050: gosub @STARTER_0
0002: jump @STARTER_PLAY_MISSION
end
00D6: if
0038: $2005 == 1 // $ == ? (int)
then
0050: gosub @STARTER_1
0002: jump @STARTER_PLAY_MISSION
end
0002: jump @STARTER_CHECK
:STARTER_PLAY_MISSION
0164: disable_marker $2004
01F7: set_player $2 ignored_by_cops 1
03BF: set_player $2 ignored_by_everyone 1
02AB: set_actor $3 immunities BP 1 FP 1 EP 1 CP 1 MP 1
01B4: set_player $2 can_move 0
016A: fade 0 time 1000
:STARTER_END
004E: end_thread
:STARTER_0
00BA: show_text_styled GXT 'BASE_1' time 1000 style 2
0417: start_mission 0
0051: return
:STARTER_1
00BA: show_text_styled GXT 'BASE_2' time 1000 style 2
0417: start_mission 1
0051: return
//------------- Mission: MISS1 (0) ---------------
:MISS1
03A4: name_thread 'MISS1'
0050: gosub @MISS1_START
00D6: if
0112: wasted_or_busted // mission only
004D: jump_if_false @MISS1_END
0050: gosub @MISS1_FAILED
:MISS1_END
0004: $409 = 0 // $ = ? (int)
00D8: mission_cleanup
004E: end_thread
:MISS1_START
0317: increment_mission_attempts
0004: $409 = 1 // $ = ? (int)
:MISS1_PASSED
0050: gosub @MISS1_CLEAR
0318: set_latest_mission_passed 'BASE_1'
0008: $2011 += 1 // $ += ? (int)
0084: $2011 = $2011 // $ = $ (int)
00D6: if
0018: $2011 > 2 // $ > ? (int)
then
0005: $2007 = 1440.0 // $ = ? (float)
0005: $2008 = 24.0 // $ = ? (float)
0005: $2009 = 13.0 // $ = ? (float)
0004: $2006 = 38 // $ = ? (int)
end
0005: $2012 = 6.0 // $ = ? (float)
0008: $2005 += 1 // $ += ? (int)
004F: create_thread @STARTER
0051: return
:MISS1_FAILED
0050: gosub @MISS1_CLEAR
004F: create_thread @STARTER
0051: return
:MISS1_CLEAR
0051: return
//------------- Mission: MISS2 (1) ---------------
:MISS2
03A4: name_thread 'MISS2'
0050: gosub @MISS2_START
00D6: if
0112: wasted_or_busted // mission only
004D: jump_if_false @MISS2_END
0050: gosub @MISS2_FAILED
:MISS2_END
0004: $409 = 0 // $ = ? (int)
00D8: mission_cleanup
004E: end_thread
:MISS2_START
0317: increment_mission_attempts
0004: $409 = 1 // $ = ? (int)
:MISS2_PASSED
0050: gosub @MISS2_CLEAR
0008: $2005 += 1 // $ += ? (int)
004F: create_thread @STARTER
0051: return
:MISS2_FAILED
0050: gosub @MISS2_CLEAR
004F: create_thread @STARTER
0051: return
:MISS2_CLEAR
0051: return
Конечно, Вы можете писать собственные стартеры вручную, если нужны экзотические способы запуска :)
Мы можем создавать собственные видео-ролики с помощью скриптового языка. Плагин "CutScene" написан с целью упростить работу с ними. Класс основан на событиях. Всего их 3: перед сценой, после сцены и действие сцены.
Перед сценой как правило загружаются модели, анимации, создаются объекты и так далее. После сцены всё это удаляется. Действие уже выполняет подачу сюжета и использует загруженные ресурсы.
Класс имеет несколько методов, которые помогут настроить некие параметры. Вот таблица этих методов:
| Команда | Описание |
|---|---|
| ClearEvents | Удаляет все события, чтобы можно было названичить их ещё раз в дальнейшем. |
| ChangeFadeColor | Изменяет цвет затемнения и осветления экрана ( по умолчанию: 0x000000 ( чёрный цвет ) ). |
| ChangeFadeTime | Устанавливает время задержки при затемнении и осветлении экрана ( по умолчанию: 500 ). |
| SetAutoInterface | Включает или отключает автоматическое прятанье элементов интерфейса, таких как радар и прочее. |
| PutPlayerActorBefore | Устанавливает позицию, куда будет перемещён игрок перед началом сцены. |
| PutPlayerActorAfter | Устанавливает позицию, куда будет перемещён игрок после конца сцены. |
| ClearPut | Позволяет указывать позицию перемещения игрока при следующем использовании. |
| Write | Генерирует код сцены ( вызывать нужно после установки событий ). |
Указать цвет и время задержки можно в конструкторе, но не обязательно ( опциональные параметры ). Рассмотрим простой пример сцены:
[Thread]
public void SCENE() {
var index = Int.local( 0 ); // 0@
var cutSceneActors = Array<Actor>.local( 1, 3 ); // 1@ 2@ 3@
var scene = new CutScene();
scene.Before = delegate {
comment = "Before";
PlayerActor.set_immunities( 1, 1, 1, 1, 1 ).lock_position( true );
PlayerChar.can_move( false );
clear_area( 1, 1400.0, 400.0, 13.0, 30.0 );
refresh_game_renderer( 1400.0, 400.0 );
load_scene( 1400.0, 400.0, 13.0 ); // Camera.SetAtPos
set_camera_behind_player();
load_model( PedModel.ARMY, PedModel.BALLAS1 );
load_requested_models();
cutSceneActors[ 0 ].create( PedType.MISSION1, PedModel.BALLAS1, 1400.0, 400.0, 13.0 );
cutSceneActors[ 1 ].create( PedType.MISSION1, PedModel.BALLAS1, 1403.0, 400.0, 13.0 );
cutSceneActors[ 2 ].create( PedType.MISSION1, PedModel.ARMY, 1405.0, 400.0, 13.0 );
set_camera_position( 1395.0, 405.0, 13.0, 0.0, 0.0, 0.0 );
set_camera_point_at( 1400.0, 400.0, 13.0, 2 );
cutSceneActors[ 1 ].task.look_at_actor( cutSceneActors[ 0 ], -1 );
cutSceneActors[ 2 ].task.look_at_actor( cutSceneActors[ 0 ], -1 );
};
scene.Action = delegate {
comment = "Action";
cutSceneActors[ 0 ].start_facial_talk( 3000 );
cutSceneActors[ 0 ].task.perform_animation( "ANIM", "PED", 4.0, 0, 0, 0, 0, 3000 );
show_text_highpriority( FXT.Add( "GXTKEY1", "Это текст диалога" ), 3000, 0 );
wait( 3000 );
cutSceneActors[ 0 ].stop_facial_talk();
};
scene.After = delegate {
comment = "After";
destroy_model( PedModel.ARMY, PedModel.BALLAS1 );
cutSceneActors.each( index, actor => {
actor.remove_references().destroy();
} );
restore_camera_with_jumpcut();
set_camera_behind_player();
PlayerActor.set_immunities( 0, 0, 0, 0, 0 ).lock_position( false );
PlayerChar.can_move( 1 );
};
scene.Write();
// scene.ClearEvents(); // <- если события "Before", "Action" и "After" нужно установить значение null
end_thread();
}
Всё это будет иметь такой вид:
//------------- SCENE --------------- :SCENE 03A4: name_thread 'SCENE' 06AB: set_actor $3 all_weapons_hidden 1 0826: enable_hud 0 0581: enable_radar 0 02A3: enable_widescreen 1 /* Before */ 02AB: set_actor $3 immunities BP 1 FP 1 EP 1 CP 1 MP 1 04D7: lock_actor $3 in_current_position 1 01B4: set_player $2 can_move 0 0395: clear_area 1 at 1400.0 400.0 13.0 radius 30.0 04E4: refresh_game_renderer_at 1400.0 400.0 03CB: set_rendering_origin_at 1400.0 400.0 13.0 0373: set_camera_directly_behind_player 0247: load_model 287 0247: load_model 102 038B: load_requested_models 009A: 1@ = create_actor_pedtype 24 model 102 at 1400.0 400.0 13.0 009A: 2@ = create_actor_pedtype 24 model 102 at 1403.0 400.0 13.0 009A: 3@ = create_actor_pedtype 24 model 287 at 1405.0 400.0 13.0 015F: set_camera_position 1395.0 405.0 13.0 rotation 0.0 0.0 0.0 0160: set_camera_point_at 1400.0 400.0 13.0 switchstyle 2 05BF: AS_actor 2@ look_at_actor 1@ -1 ms 05BF: AS_actor 3@ look_at_actor 1@ -1 ms 0169: set_fade_color_RGB 0 0 0 016A: fade 1 time 500 0707: start_scene_skip_to @SCENE_AUTO_SCENE_0 /* Action */ 0967: actor 1@ move_mouth 3000 ms 0605: AS_actor 1@ perform_animation "ANIM" IFP "PED" framedelta 4.0 loopA 0 lockX 0 lockY 0 lockF 0 time 3000 ms // version A 00BC: show_text_highpriority GXT 'GXTKEY1' time 3000 flag 0 0001: wait 3000 ms 0968: actor 1@ stop_mouth :SCENE_AUTO_SCENE_0 0169: set_fade_color_RGB 0 0 0 016A: fade 0 time 500 0701: end_scene_skip 0001: wait 500 ms 02A3: enable_widescreen 0 0826: enable_hud 1 0581: enable_radar 1 06AB: set_actor $3 all_weapons_hidden 0 /* After */ 0249: release_model 287 0249: release_model 102 for 0@ = 0 to 3 01C2: remove_references_to_actor 1@(0@,3i) // Like turning an actor into a random pedestrian 009B: destroy_actor 1@(0@,3i) end 02EB: restore_camera_with_jumpcut 0373: set_camera_directly_behind_player 02AB: set_actor $3 immunities BP 0 FP 0 EP 0 CP 0 MP 0 04D7: lock_actor $3 in_current_position 0 01B4: set_player $2 can_move 1 0169: set_fade_color_RGB 0 0 0 016A: fade 1 time 500 0001: wait 500 ms 004E: end_thread
Мы видим, что у нас сцена начинается и заканчивается с осветления экрана. Мы можем повлиять на это, передав в метод "Write" опциональные параметры. Также рассмотрим ситуацию, когда в начале и вконце сцены нужно перемещать игрока:
[Thread]
public void SCENE() {
var scene = new CutScene();
// указываем куда перемещать игрока до сцены
scene.PutPlayerActorBefore( 1400.0, 25.0, 13.0, 180.0 );
// указываем куда перемещать игрока после сцены
scene.PutPlayerActorAfter( 1300.0, 125.0, 13.0, 0.0 );
scene.Action = delegate {
comment = "Action";
};
scene.Write( true, false ); // в начале сцены будет осветление, а в конце - нет
// если в дальнейшем не надо перемещать игрока или нужно перемещать в другие позиции
scene.ClearPut();
end_thread();
}
Опциональные параметры метода "Write" позволяют контролировать наличие осветления и затемнения. В итоге будет это:
//------------- SCENE --------------- :SCENE 03A4: name_thread 'SCENE' 0169: set_fade_color_RGB 0 0 0 // если указать первым параметром "true" в методе "Write", то будут эти две строчки 016A: fade 0 time 500 0001: wait 500 ms // если вызвать метод "PutPlayerActorBefore" 00A1: put_actor $3 at 1400.0 25.0 14.0 0173: set_actor $3 z_angle_to 180.0 06AB: set_actor $3 all_weapons_hidden 1 0826: enable_hud 0 0581: enable_radar 0 02A3: enable_widescreen 1 0169: set_fade_color_RGB 0 0 0 016A: fade 1 time 500 0707: start_scene_skip_to @SCENE_AUTO_SCENE_0 /* Action */ :SCENE_AUTO_SCENE_0 0169: set_fade_color_RGB 0 0 0 016A: fade 0 time 500 0701: end_scene_skip 0001: wait 500 ms 02A3: enable_widescreen 0 0826: enable_hud 1 0581: enable_radar 1 06AB: set_actor $3 all_weapons_hidden 0 /* если указать вторым параметром "false" в методе "Write", то следующего кода не будет: 0169: set_fade_color_RGB 0 0 0 016A: fade 1 time 500 0001: wait 500 ms */ // если вызвать метод "PutPlayerActorAfter" 00A1: put_actor $3 at 1300.0 125.0 14.0 0173: set_actor $3 z_angle_to 0.0 004E: end_thread
Используя один класс, мы можем генерировать несколько сцен подряд или использовать в нескольких потоках. Давайте рассмотрим пример, когда такая ситуация есть:
[Thread]
public void SCENE() {
var scene = new CutScene();
scene.SetAutoInterface( false ); // не прячем радар, HUD
scene.Action = delegate { comment = "Action #1"; };
scene.Write( false, false );
comment = "--- --- ---";
comment = "--- --- ---";
comment = "--- --- ---";
scene.Action = delegate { comment = "Action #2"; };
scene.Write( false, true );
end_thread();
}
Нам не обязательно указывать все события сцены. Нужно указать метод хотя бы для "Action". После метода "Write" можно переопределить действие и генерировать другое действие сцены. Метод "SetAutoInterface" позволяет немного сократить код. Вот такой результат будет у нас:
//------------- SCENE --------------- :SCENE 03A4: name_thread 'SCENE' 0169: set_fade_color_RGB 0 0 0 016A: fade 1 time 500 0707: start_scene_skip_to @SCENE_AUTO_SCENE_0 /* Action #1 */ :SCENE_AUTO_SCENE_0 0169: set_fade_color_RGB 0 0 0 016A: fade 0 time 500 0701: end_scene_skip 0001: wait 500 ms /* --- --- --- */ /* --- --- --- */ /* --- --- --- */ 0169: set_fade_color_RGB 0 0 0 016A: fade 1 time 500 0707: start_scene_skip_to @SCENE_AUTO_SCENE_1 /* Action #2 */ :SCENE_AUTO_SCENE_1 0169: set_fade_color_RGB 0 0 0 016A: fade 0 time 500 0701: end_scene_skip 0001: wait 500 ms 0169: set_fade_color_RGB 0 0 0 016A: fade 1 time 500 0001: wait 500 ms 004E: end_thread
Конечно, можно обойтись и без плагина, так как реализация сцен не такая сложная.
Подключение плагинов добавляет некоторые классы, которые имеют туже цель, что и классы в Sanny Builder. К некоторым вещам настолько привыкаешь, что больше без них не можешь обойтись :) Я добавил классы "Model", "Garage" и "Camera". Все они статические. Другие нет смысла добавлять, так как уже есть готовые объекты генератора.
Использование очень простое:
[Thread]
public void TEST() {
Model.Load( PedModel.ARMY );
load_requested_models();
jf( DefaultWaitTime, Model.Available( PedModel.ARMY ) );
Model.Destroy( PedModel.ARMY );
Camera.RestoreWithJumpcut();
Camera.BehindPlayer();
Garage.Activate( "Name" );
end_thread();
}
Думаю, Вы поймёте что будет в итоге :) На текущий момент это все плагины и дополнения.