Korzystając z okazji, że na forum pojawił się poradnik o jumpach i gosubach, chcę objaśnić coś, co jest używane dość rzadko, a ma ogromne możliwości i jest nieco spokrewnione z w/w opcodami. Mowa tu o funkcjach SCM (SCM functions). Można je porównać do gosubu, jednak taka funkcja posiada swoje własne zmienne, potrafi otrzymywać wartości z "macierzystego" wątku, a także je zwracać.
Standardowy opcode uruchamiający funkcję SCM wygląda tak:
Kod:
0AB1: call_scm_func
@GetSQR 1 10
$result
Co jest czym? Więc po kolei:
- @GetSQR - nagłówek, do którego funkcja ma skoczyć - podobnie jak w przypadku gosubu, funkcja wróci do miejsca, z którego została wywołana, poprzez opcode 0AB2 (w przypadku zwykłego gosubu było to 0051);
- 1 - liczba wartości, które zostają przesłane do funkcji;
- 10 - wartość przesyłana (może to być liczba całkowita [int], liczba zmiennoprzecinkowa [float], a także zmienna lokalna lub globalna);
- $result - zmienna, do której funkcja zwraca określone wartości.
Tak, wszystko wydaje się niezrozumiałe, i na razie takie jest. A więc, na czym polega przesyłanie wartości do funkcji? Domyślnie, taka wywołana funkcja SCM wszystkie wartości ma wyzerowane (a przynajmniej powinien - zauważyłem, że CLEO3 nie zawsze to respektuje, CLEO4 nie ma tego błędu), a w większości przypadków chcemy je samemu ustawić już w momencie jej wywoływania. Z racji, że ta funkcja ma je wszystkie osobne i zmienne z normalnego wątku
w żaden sposób nie są nadpisywane, trzeba to zrobić właśnie w opcodzie wywołującym, czyli
0AB1. Oznaczamy liczbą ilość wartości, które chcemy przekazać funkcji (w przykładzie powyżej jest to
1), a następnie definiujemy, co mamy przesłać (w przykładzie jest to zmienna
0@ zostanie ustawiona na
10; jeśli ustawilibyśmy np. 8 wartości do przekazania, przesłalibyśmy wartości do zmiennych
0@-7@). Po zdefiniowaniu tego możemy także ustalić, gdzie funkcja ma zwrócić swoje wartości - liczby nie ustawiamy w opcode
0AB1, ustalamy za to przy
0AB2, ale o tym za momencik.
Podam przykład funkcji SCM, która wyliczy nam długość przeciwprostokątnej trójkąta prostokątnego o znanych obu przyprostokątnych, czyli sławne twierdzenie Pitagorasa:
Gdzieś w skrypcie:
Kod:
0AB1: call_scm_func
@Pitagoras 2 12.0
4@ 0@
12.0 i
4@ to długości przyprostokątnych, które w funkcji SCM będą odpowiednio w zmiennych
0@ i
1@.
0@, czyli zmienna wątku głównego, będzie zawierała zwróconą wartość przeciwprostokątnej.
Sama funkcja:
Kod:
Zakładając, że w zmiennej
4@ mamy wartość
5.0, funkcja wyliczy nam przeciwprostokątną równą
13.0. Ale jak ją zwróci? Robi to opcode
0AB2, w którym ustalamy, ile i jakie zmienne mają zostać zwrócone. Wartość pierwsza musi być równa liczbie zmiennych, które "przygotowaliśmy" w czasie wywoływania opcodu
0AB1.
2@ zawiera wartość, która z funkcji zostanie zwrócona do pierwszej zmiennej odbierającej (w tym przypadku pierwszej i jedynej, czyli
0@).
Główne pytanie - czemu nie gosub? Odpowiedź jest prosta - gosub naruszy nam nasze zmienne z normalnego wątku. Twierdzenie Pitagorasa nie wymagało ich wiele, więc nie jest to najlepszy przykład - najlepiej te funkcje sprawdzają się w przypadku, gdy mamy niewiele wolnych zmiennych lokalnych, a potrzebujemy obliczyć coś naprawdę skomplikowanego (bez funkcji SCM GPS nie mógłby istnieć :) ).
Może wydawać się to trudne. Na początku takie jest, ale należy przeczytać to minimum parę razy - to się naprawdę da opanować.
SA ma sporo możliwości tworzenia skryptów SCM w sposób, w jaki Rockstar nigdy nie działał. Jednym z takich trików jest użycie gosubu i funkcji SCM jako warunku.
Użycie tego jest bardzo, bardzo proste. Wszystko można objaśnić, używając kawałku funkcji z modu GPS.
Kod:
[...]
if
0AB1: call_scm_func
@GPS__FUNKCJA 3 RequestedCoordsOffset RequestedCoordsY
0@ [...]
:GPS__FUNKCJA
[...]
if and
3.0 > DistanceXY
10.0 > DistanceZ
then
0485: return_true
else
059A: return_false
end
[...]
Jak nietrudno się domyśleć na podstawie tego przykładu, o tym, czy gosub zwróci prawdę lub fałsz decydują dwa opcody. I tak
0485 ustawia "wynik" na "prawda", a
059A na "fałsz". One mogą się wzajemnie nadpisywać, więc możliwa jest wielokrotna zmiana wyniku w trakcie jednego sprawdzenia.
Funkcja, której wyżej kawałek został użyty jako przykład, podczas sprawdzania używa prawie 20 zmiennych. Dzięki temu widać, jak opłacalne są funkcje SCM - bez nich taka rozrzutność byłaby niemożliwa.
Jedna adnotacja co do tej metody: z racji, że gosub i funkcja SCM same z siebie nie są warunkami, to
nie można używać ich w jednym sprawdzeniu razem z normalnymi opcodami lub dwóch gosubów na raz. W takim razie odpadają sprawdzenia typu:
Kod:
lub
Kod:
if or
00DF: actor
$PLAYER_ACTOR driving
0050: gosub @C
Takie konstrukcje
muszą być rozłożone na oddzielne sprawdzenia.
W jaki sposób działa warunek złożony z funkcji SCM/gosubu?
Ten trik wykorzystuje specyficzny sposób pojmowania warunków poprzez GTA. A mianowicie, rezultat sprawdzenia takiego warunku jest przechowywany w nieskończoność (więcej informacji w języku angielskim
TUTAJ) - tak więc gosuby w ogóle nie są warunkami, jedynie opcody
return_true i
return_false ustawiają rezultat sprawdzenia w odpowiedni sposób.
To wyjaśnia też, czemu gosuby/funkcje SCM nie mogą być użyte w "
if and" oraz w "
if or" - nie są one parsowane jako warunkowe.
Idąc tym tropem, w niektórych przypadkach możliwe jest pozbycie się opcodów
0485 i
059A. Kiedy można ich nie używać? Na przykład wtedy:
Kod:
Tłumacząc na "ludzki", powyższy kod wygląda tak jakby
Kod:
jeśli wynik sprawdzenia to TAK
ustaw wynik sprawdzenia na TAK
w przeciwnym wypadku ustaw na NIE
Opcody ustawiające sprawdzenie są w tym momencie zbędne, gdyż wynik sprawdzenia 0019 będzie identyczny z ustawieniami return_true i return_false (powyższy kod wygląda po prostu idiotycznie, a ten SCMowy jest dokładnie tak samo idiotyczny dla gry ;) ). Możemy więc to skrócić do:
Kod:
Jeszcze jedna ciekawostka - działanie opcodów
0485 i
059A może być symulowane przez niewielki hack pamięci wątku, np.
Kod:
0A9F: 0@ = current_thread_pointer
000A: 0@ += 0xC5
0A8C: write_memory
0@ size 1 value
true virtual_protect 0
Wpisanie wartości
true spowoduje oznaczenie sprawdzenia jako "prawda", a
false - "fałsz". Ta operacja nie ma jednak zbyt dużego sensu (zajmuje więcej miejsca w pliku CS/SCM i jest wolniejsza).