Трюки с метками

Материал из GTAModding.ru
Перейти к: навигация, поиск

Общепринятое обращение с метками весьма узко: jump на метку и всё. Но реальные возможности для работы с метками гораздо шире. Сейчас я вам покажу вам несколько трюков с метками.

Трюк первый: хранение метки в переменной и динамический jump

Sanny Builder позволяет присваивать переменным метку. Например:

0@ = @MyLabel

и потом можно прыгнуть на эту сохранённую в переменной метку:

jump 0@

Вот пример кода:

  1. :Begin
  2. 0@ = @MyLabel
  3. jump 0@
  4.  
  5. :MyLabel
  6. //...

Для чего это нужно? Ну например для динамического изменения кода скрипта по ходу игры. Или если какая-то проверка или часть кода не нужны по ходу игры, то можно их убрать.

Трюк второй: условный Gosub

Все знают, что gosub - это команда безусловного перехода на метку с последующим возвратом по команде return. Но не все знают, что gosub можно использовать как условие. Это бывает очень полезно, когда, например, в цикле вам нужно одновременно проверить несколько разнородных условий, а объединить их нельзя. Тогда на помощь приходит gosub. Seemann использовал этот трюк ранее, например в первой версии CLEO, и он неплохо себя зарекомендовал.

  1. if
  2.  
  3.   gosub @CheckDriverIsMale
  4.  
  5. jf @gosubFalse

В чем подвох, спросите вы? Дело в том, что такой gosub потребует особого оформления результата при выходе. Чтобы gosub вернул True (и проверка сработала) или False (и проверка не сработала) нужно добавить перед return соответственно опкод 0485: return_true или 059A: return_false.

Пример:

  1. :CheckDriverIsMale
  2. 046C: 3@ = car 9@ driver
  3. if
  4.  3@ <> -1
  5. jf @ReturnFalse
  6. if
  7. 03A3:   actor 3@ male 
  8. jf @ReturnFalse
  9. 0485: return_true
  10. return
  11.  
  12.  
  13. :ReturnFalse
  14. 059A: return_false
  15. return

Таким образом gosub @CheckDriverIsMale вернет True, если за рулем машины 9@ сидит мужчина, иначе он вернет False. Бывает удобно использовать такой метод, когда вам нужно несколько раз проверять условие, требующее нескольких опкодов (например, вы в цикле в разных местах проверяете водителя-мужчину).

Трюк третий: прыжок на метку в зависимости от переменной

Можно высчитать значение метки и прыгнуть на неё в зависимости от значения переменной. Так как переменная может быть больше или меньше, чем предусмотрено, то при прыжке при непредусмотренном значении переменной игра может наткнуться на середину опкода, и зависнуть. Поэтому нужно в начале проверить границы переменной.

Для начала в Sanny Builder 3 нажмите кнопку "Отладочные опции" на панели инструментов, и выберите пункт CODE_OFFSETS. Тогда при декомпиляции Cleo-скрипта на каждой строчке будет показываться его оффсет (смещение) с начала файла.

Создайте новый файл и наберите код:

  1. {$CLEO}
  2. jump @Label
  3. jump @Label
  4. jump @Label
  5. jump @Label
  6. :Label

А теперь нажмите клавишу F6 чтобы компилить скрипт не копируя его в папку, и декомпилируйте его. Появится код:

  1. // This file was decompiled using sascm.ini published by Seemann (http://sannybuilder.com/files/SASCM.rar) on 13.10.2007
  2.  
  3. {$VERSION 3.1.0027}
  4. {$CLEO .cs}
  5.  
  6. //-------------MAIN---------------
  7. {0} jump @Noname_28 
  8. {7} jump @Noname_28 
  9. {14} jump @Noname_28 
  10. {21} jump @Noname_28 
  11.  
  12. :Noname_28 // Note: a jump to this label will crash the game

число в фигурных скобках - смещение опкода от начала файла в байтах. Разные опкоды могут иметь разную длину, более того, один и тот же опкод может иметь разную длину если у него есть параметры-числа или строки. Опкоды jump, gosub, return имеют всегда одинаковую длину (если параметр типа @label), поэтому легко высчитать смещение от начала файла, и прыгнуть на них.

Посмотрим на код. jump имеет длину семь байтов, так как смещение каждый раз изменяется на семь.

В Cleo, External, Mission скриптах используется локальное смещение, оно имеет всегда отрицательное значение. В Main-скриптах используется глобальное смещение, оно всегда неотрицательно.

Вот сам код:

  1. var
  2.   0@: Integer
  3.   1@: Integer
  4.   2@: Integer
  5. end
  6. {...}
  7. if and
  8.   0@>=0
  9.   0@<=3
  10. then
  11.   1@ = @Label
  12.   2@ = 0@
  13.   2@ *= -7 // Если не Cleo-скрипт, то минус убрать!
  14.   1@ += 2@
  15.   jump 1@
  16.   :continue
  17. end
  18. {...}
  19. 0A93: end_custom_thread
  20.  
  21. :Label
  22. jump @Case0
  23. jump @Case1
  24. jump @Case2
  25. jump @Case3
  26.  
  27. :Case0
  28. {Дестия}
  29. jump @continue
  30.  
  31. :Case1
  32. {Дестия}
  33. jump @continue
  34.  
  35. :Case2
  36. {Дестия}
  37. jump @continue
  38.  
  39. :Case3
  40. {Дестия}
  41. jump @continue

или другой пример:

  1. var
  2.   0@: Integer
  3.   1@: Integer
  4.   2@: Integer
  5. end
  6. {...}
  7. if and
  8.   0@>=0
  9.   0@<=3
  10. then
  11.   1@ = @Label
  12.   2@ = 0@
  13.   2@ *= -9
  14.   1@ += 2@
  15.   gosub 1@
  16. end
  17. {...}
  18. 0A93: end_custom_thread
  19.  
  20. :Label
  21. gosub @Case0
  22. return
  23. gosub @Case1
  24. return
  25. gosub @Case2
  26. return
  27. gosub @Case3
  28. return
  29.  
  30. :Case0
  31. {Дестия}
  32. return
  33.  
  34. :Case1
  35. {Дестия}
  36. return
  37.  
  38. :Case2
  39. {Дестия}
  40. return
  41.  
  42. :Case3
  43. {Дестия}
  44. return

Конечно, в San Andreas в 95% случаев рекомендуется пользоваться Jump tables, а не этот способ. Этот способ впервые был применён в Vice City в GTA Mission Assembler.