0010. Таблицы переходов - wmysterio/gta-script-generator GitHub Wiki
В скриптах для GTA San Andreas есть возможность использовать таблицы переходов. Опкод имеет громадный размер, и постоянно редактировать метки, добавление или удаление блоков было очень нудным и хлопотным делом. Генератор скриптов существенно упрощает написание подобного рода код. Вот так можно написать код таблицы переходов:
public class TEST : Thread {
Int anyInt;
public override void START( LabelJump label ) {
anyInt.random( 0, 10 );
jump_table( anyInt, jt => {
// тут будут создаваться метки для переходов
} );
end_thread();
}
}
Здесь jt
— это объект, который позволит нам создавать переходы и ассоциировать их с числом. Ассоциация происходит с помощью метода label
. Пример использования:
public class TEST : Thread {
Int anyInt;
public override void START( LabelJump label ) {
anyInt.random( 0, 10 );
jump_table( anyInt, jt => {
jt.label( 0, label => {
Comment = "case #0";
} );
jt.label( 1, label => {
Comment = "case #1";
} );
jt.label( 2, delegate {
Comment = "case #2";
} );
// или использовать цепочку:
jt.label( 3, label => {
Comment = "case #3";
} ).label( 4, label => {
Comment = "case #4";
} ).label( 5, label => {
Comment = "case #5";
} ).label( 6, label => {
Comment = "case #6";
} ).label( 7, delegate {
Comment = "case #7";
} ).label( 8, delegate {
Comment = "case #8";
} ).label( 9, delegate {
Comment = "case #9";
} ).label( 10, delegate {
Comment = "case #10";
} );
} );
end_thread();
}
}
Если количество блоков превысит максимальный размер, то автоматически будет создано продолжение таблицы переходов, как это показано ниже, в результате:
//------------- THREAD TEST ---------------
:TEST
03A4: name_thread 'TEST'
0209: 0@ = random_int_in_ranges 0 10
0871: init_jump_table 0@ total_jumps 11 default_jump 0 @TEST_SWITCH_0_CASE_END jumps 0 @TEST_SWITCH_0_CASE_0 1 @TEST_SWITCH_0_CASE_1 2 @TEST_SWITCH_0_CASE_2 3 @TEST_SWITCH_0_CASE_3 4 @TEST_SWITCH_0_CASE_4 5 @TEST_SWITCH_0_CASE_5 6 @TEST_SWITCH_0_CASE_6
0872: jump_table_jumps 7 @TEST_SWITCH_0_CASE_7 8 @TEST_SWITCH_0_CASE_8 9 @TEST_SWITCH_0_CASE_9 10 @TEST_SWITCH_0_CASE_10 -1 @TEST_SWITCH_0_CASE_END -1 @TEST_SWITCH_0_CASE_END -1 @TEST_SWITCH_0_CASE_END -1 @TEST_SWITCH_0_CASE_END -1 @TEST_SWITCH_0_CASE_END
:TEST_SWITCH_0_CASE_0
// case #0
:TEST_SWITCH_0_CASE_1
// case #1
:TEST_SWITCH_0_CASE_2
// case #2
:TEST_SWITCH_0_CASE_3
// case #3
:TEST_SWITCH_0_CASE_4
// case #4
:TEST_SWITCH_0_CASE_5
// case #5
:TEST_SWITCH_0_CASE_6
// case #6
:TEST_SWITCH_0_CASE_7
// case #7
:TEST_SWITCH_0_CASE_8
// case #8
:TEST_SWITCH_0_CASE_9
// case #9
:TEST_SWITCH_0_CASE_10
// case #10
:TEST_SWITCH_0_CASE_END
004E: end_thread
В примере я использовал делегат. Он нужен в том случае, если нет необходимости делать переходы на метки, которые генерируются методом label
. Если эту метку нужно использовать (например: при условиях), то пользуемся анонимным методом с сигнатурой label =>
вместо delegate. Объект jt также имеет свойство EndLabel
, которое хранит метку выхода с таблицы. Она генерируется в конце всех блоков. Пример:
public class TEST : Thread {
Int anyInt;
public override void START( LabelJump label ) {
anyInt.random( 0, 10 );
jump_table( anyInt, jt => {
jt.label( 0, label => {
wait( 0 );
and( label, PlayerChar.is_defined() ); // переход на текущую CASE-метку, если условие не выполняется
Comment = "case #0";
jump( jt.EndLabel ); // переход в конец таблицы
} );
jt.label( 1, label => {
wait( 0 );
Comment = "case #1";
jump( label ); // переход на текущую CASE-метку
} );
jt.label( 2, delegate {
Comment = "case #2";
jump( jt.EndLabel ); // переход в конец таблицы
} );
} );
// здесь будет метка выхода с таблицы
wait( 2500 );
end_thread();
}
}
Метку конца таблицы также можно получить с объекта label в данном примере:
//...
jt.label( 0, label => {
Comment = "case #0";
jump( label.EndLabel ); // переход в конец таблицы
} );
//...
Результат:
//------------- THREAD TEST ---------------
:TEST
03A4: name_thread 'TEST'
0209: 0@ = random_int_in_ranges 0 10
0871: init_jump_table 0@ total_jumps 3 default_jump 0 @TEST_SWITCH_0_CASE_END jumps 0 @TEST_SWITCH_0_CASE_0 1 @TEST_SWITCH_0_CASE_1 2 @TEST_SWITCH_0_CASE_2 -1 @TEST_SWITCH_0_CASE_END -1 @TEST_SWITCH_0_CASE_END -1 @TEST_SWITCH_0_CASE_END -1 @TEST_SWITCH_0_CASE_END
:TEST_SWITCH_0_CASE_0
0001: wait 0 ms
00D6: if
0256: player $2 defined
004D: jump_if_false @TEST_SWITCH_0_CASE_0
// case #0
0002: jump @TEST_SWITCH_0_CASE_END
:TEST_SWITCH_0_CASE_1
0001: wait 0 ms
// case #1
0002: jump @TEST_SWITCH_0_CASE_1
:TEST_SWITCH_0_CASE_2
// case #2
0002: jump @TEST_SWITCH_0_CASE_END
:TEST_SWITCH_0_CASE_END
0001: wait 2500 ms
004E: end_thread
Если значения "кейсов" возрастают от нуля каждый раз на единицу, то можно воспользоваться событием Auto. Вместо анонимных функций более компактный вид имеет следующий код:
public class TEST : Thread {
Int anyInt;
public override void START( LabelJump label ) {
anyInt.random( 0, 10 );
jump_table( anyInt, jt => {
// В столбик добавляем новый обработчик каждого кейса
jt.Auto += case_0; // anyInt == 0
jt.Auto += case_1; // anyInt == 1
jt.Auto += case_2; // anyInt == 2
jt.Auto += case_3; // anyInt == 3
void case_0( LabelCase label ) { // локальная функция внутри метода "jump_table"
Comment = "case #0";
jump( jt.EndLabel );
}
void case_1( LabelCase label ) { // локальная функция внутри метода "jump_table"
Comment = "case #1";
jump( jt.EndLabel );
}
void case_2( LabelCase label ) { // локальная функция внутри метода "jump_table"
Comment = "case #2";
jump( jt.EndLabel );
}
void case_3( LabelCase label ) { // локальная функция внутри метода "jump_table"
Comment = "case #3";
jump( jt.EndLabel );
}
} );
// здесь будет метка выхода с таблицы
wait( 2500 );
end_thread();
}
}
Так, как у нас нет возможности использовать объект jt вне метода START (в этом примере), то все функции нужно сделать локальными. Также они позволяют не разбрасывать код по всему файлу. Вот результат:
//------------- THREAD TEST ---------------
:TEST
03A4: name_thread 'TEST'
0209: 0@ = random_int_in_ranges 0 10
0871: init_jump_table 0@ total_jumps 4 default_jump 0 @TEST_SWITCH_0_CASE_END jumps 0 @TEST_SWITCH_0_CASE_0 1 @TEST_SWITCH_0_CASE_1 2 @TEST_SWITCH_0_CASE_2 3 @TEST_SWITCH_0_CASE_3 -1 @TEST_SWITCH_0_CASE_END -1 @TEST_SWITCH_0_CASE_END -1 @TEST_SWITCH_0_CASE_END
:TEST_SWITCH_0_CASE_0
// case #0
0002: jump @TEST_SWITCH_0_CASE_END
:TEST_SWITCH_0_CASE_1
// case #1
0002: jump @TEST_SWITCH_0_CASE_END
:TEST_SWITCH_0_CASE_2
// case #2
0002: jump @TEST_SWITCH_0_CASE_END
:TEST_SWITCH_0_CASE_3
// case #3
0002: jump @TEST_SWITCH_0_CASE_END
:TEST_SWITCH_0_CASE_END
0001: wait 2500 ms
004E: end_thread
Если не использовать локальные функции, то доступ к метке конца таблицы можно получить с объекта LabelCase, воспользовавшись свойством EndJumpTable
:
public class TEST : Thread {
Int anyInt;
public override void START( LabelJump label ) {
anyInt.random( 0, 10 );
jump_table( anyInt, jt => {
jt.Auto += case_0; // anyInt == 0
jt.Auto += case_1; // anyInt == 1
jt.Auto += case_2; // anyInt == 2
jt.Auto += case_3; // anyInt == 3
} );
// здесь будет метка выхода с таблицы
wait( 2500 );
end_thread();
}
private void case_0( LabelCase label ) {
Comment = "case #0";
jump( label.EndJumpTable ); // переход на метку выхода вне конструкции "jump_table"
}
private void case_1( LabelCase label ) {
Comment = "case #1";
jump( label.EndJumpTable );
}
private void case_2( LabelCase label ) {
Comment = "case #2";
jump( label.EndJumpTable );
}
private void case_3( LabelCase label ) {
Comment = "case #3";
jump( label.EndJumpTable );
}
}