Concluindo esta parte você vai:
Saber responder o que é, como usar e quais as vantagens do uso do GOSUB e CLEO_CALL em seu script.
Também terá dicas ao longo do tutorial, na qual te ajudará a pensar melhor sobre a organização dos seus scripts.
Nada precisa ser repetitivo
Você já deve estar começando a criar alguns scripts simples, e já pode começar a imaginar melhores métodos para se fazer a mesma coisa.
Em programação (assim como várias outras áreas!) pode ser feita de incontáveis maneiras diferentes dependente do raciocínio da pessoa, assim como há formas "boas" e "ruins" de fazer algumas coisas.
Uma das coisas ruins, é a alta repetição da mesma coisa, o que poderia ser feita somente uma vez e chamada várias vezes! Ou simplesmente a mudança de posição de um código, criando uma melhor organização para facilitar a leitura.
GOSUB - Sub tarefa
O comando
GOSUB
("go sub", leia isso como "ir para uma sub-tarefa") é usado para ir para uma "tarefa secundária", e voltar de onde veio.
Edit: Anos depois, estudando C#, eu descobri que isto é o equivalente à "
Local Functions". Ou seja, se você já tem experiência em programação, fica mais fácil de você entender.
É muito parecido com o
GOTO
, a única diferença é que ele foi feito para ir e voltar, ou seja, temos o comando
RETURN
para retornar de onde saiu:
Código: Selecionar tudo
SCRIPT_START
{
NOP
main_loop:
WAIT 0
IF IS_KEY_PRESSED VK_KEY_Y
GOSUB label
ENDIF
GOTO main_loop
label:
PRINT_STRING_NOW "Oi!" 1
RETURN
}
SCRIPT_END
Sim, isso é extremamente simples e o tutorial já até poderia acabar por aqui...
Vamos imaginar que você ainda não entendeu. Bem, você já sabe muito bem como o
GOTO
funciona, né?
Então veja isto:
Código: Selecionar tudo
SCRIPT_START
{
NOP
main_loop:
WAIT 0
IF IS_KEY_PRESSED VK_KEY_Y
GOTO label
label2:
ENDIF
GOTO main_loop
label:
PRINT_STRING_NOW "Oi!" 1
GOTO label2
}
SCRIPT_END
É a mesma coisa! Mas vamos ser sinceros, ficou confuso e feio, né? O
GOSUB
é muito mais elegante.
E como sempre, sinta-se livre! Você pode fazer um dentro do outro:
Código: Selecionar tudo
SCRIPT_START
{
NOP
main_loop:
WAIT 0
IF IS_KEY_PRESSED VK_KEY_Y
GOSUB label
ENDIF
GOTO main_loop
label:
PRINT_STRING_NOW "Oi!" 1
IF IS_KEY_PRESSED VK_KEY_H
GOSUB outra
ENDIF
RETURN
outra:
PRINT_STRING_NOW "Simples." 1
RETURN
}
SCRIPT_END
Conseguiu entender o que vai acontecer? Leia o código e interprete-o.
(no momento o fórum não tem contagem de linhas, copie e cole no VS Code para ver o número de cada linha)
Ao segurar a tecla
Y
irá chamar um
GOSUB
que aparece
Oi!
e checa se apertou a tecla
H
. Se apertou, entra em um segundo
GOSUB
para mostrar a mensagem
Simples.
e volta. Quando voltar, o script voltará de onde este segundo
GOSUB
saiu (ali na linha 15!!!) e continuar o seu caminho. Até encontrar o outro
RETURN
(ali na linha 17), e voltar lá no início: Na linha 8.
O limite é de 8
GOSUB
s ao mesmo tempo. Parece pequeno, mas, sério, eu gosto de
GOSUB
e uso demais nos meus mods, e em toda a minha vida eu nunca sofri este limite. Acredito que você sofrerá só caso usar ele exageradamente.
O que é "
GOSUB
ao mesmo tempo"? É um dentro do outro sem retornar, por exemplo você chamar um GOSUB, e este GOSUB chamar outros 8 ou mais sem nunca chamar um
RETURN
, causará crash no jogo.
É sim muito simples de entender, é só um vai e volta. E no exemplo que eu falei acima foi: Vai, vai, volta, volta. Entendeu?
Depois de ter certeza de ter entendido o que é
GOSUB
, você já terá facilidade de entender os princípios de um
CLEO_CALL
:
CLEO_CALL - Uma sub-tarefa mais avançada
Eu adoro CLEO_CALL
!
Ele é igual um
GOSUB
— inicie sua mente com isso, pense num
GOSUB
! — mas a diferença é que o que acontece num
CLEO_CALL
, não influencia no que está fora dele.
Se você entende de outras programações: CLEO_CALL
é simplesmente chamar uma função, como você já deve conhecer.
Se você entende de Sanny Builder: CLEO_CALL
é call_scm_func
, uma função SCM.
Vamos devagar. Veja a comparação:
É A MESMA COISA.
"E aquele "0" ali?"
NÃO IMPORTA, ele nem é utilizado (antigamente ele era, você saberá o motivo). Simplesmente ignore o "0".
Tá, agora vamos para as diferenças:
Código: Selecionar tudo
SCRIPT_START
{
LVAR_INT var1
var1 = 12
main_loop:
WAIT 0
IF IS_KEY_PRESSED VK_KEY_Y
GOSUB label
ENDIF
GOTO main_loop
label:
PRINT_FORMATTED_NOW "%i" 1 var1
RETURN
}
SCRIPT_END
Isso irá mostrar na tela o valor da variável
var1
quando você apertar a tecla
Y
. Qual o valor?
12
. Um número que você já deve gostar.
E se fizermos igual, mas com
CLEO_CALL
?
Código: Selecionar tudo
SCRIPT_START
{
LVAR_INT var1
var1 = 12
main_loop:
WAIT 0
IF IS_KEY_PRESSED VK_KEY_Y
CLEO_CALL label 0
ENDIF
GOTO main_loop
label:
PRINT_FORMATTED_NOW "%i" 1000 var1
CLEO_RETURN 0
}
SCRIPT_END
Vai aparecer "0" :)
"What?"
As variáveis existentes deixam de existir quando você chama um
CLEO_CALL
!
Lá dentro não existe o que há lá fora.
Não importa que a
var1
seja
12
lá fora. Dentro do
CLEO_CALL
ela não existe! Ela tem o valor
0
(pois todas as variáveis, por padrão, têm o valor
0
).
Mas calma, o importante está por vir:
Nós podemos enviar a nossa var1
para o CLEO_CALL
!
Envio e retorno de valores
É agora que a coisa fica interessante!
Vamos virar de cabeça pra baixo:
Pela primeira vez aqui no tutorial você verá um script com o uso de 2 escopos.
Vou começar a indentar (adicionar um TAB) dentro do escopo, para facilitar a identificação de onde começa e termina eles. Você pode preferir fazer assim em seus scripts.
Também usarei os carácteres
(
)
,
para deixar o código melhor para ler. Você ainda não deve saber, mas estes carácteres são tratados como espaço no compilador, ou seja, eles não influenciam em absolutamente nada no seu código, somente visualmente! Então eles têm certa utilidade, como separar variáveis para ficar melhor para ler.
Olhe bem e preste muita atenção neste script:
Código: Selecionar tudo
SCRIPT_START
{
LVAR_INT var1
var1 = 12
main_loop:
WAIT 0
CLEO_CALL label 0 (var1)(var1)
PRINT_FORMATTED_NOW "%i" 1 var1
GOTO main_loop
}
SCRIPT_END
{
LVAR_INT in_var
label:
in_var = 13
CLEO_RETURN 0 (in_var)
}
Mostrará
13
na tela, corretamente.
Perceba o
(var1)(var1)
O primeiro
(var1)
é a variável enviada para dentro do
CLEO_CALL
. Chegando lá, o valor desta variável entrará na primeira variável declarada lá dentro, neste caso, a variável
in_var
. Ou seja, lá dentro do
CLEO_CALL
, a
in_var
terá o valor da
var1
, que é
12
.
Mas eu mudei o valor, e agora será
13
.
No
CLEO_RETURN
eu retornei a variável
in_var
.
Ao retornar, o valor dela irá para o segundo
(var1)
, ou seja, o valor da
in_var
irá para a
var1
lá fora do
CLEO_CALL
.
Agora
var1
tem o valor
13
.
O mesmo aconteceria se fosse assim, usando uma segunda variável:
Código: Selecionar tudo
SCRIPT_START
{
LVAR_INT var1 var2
var1 = 12
main_loop:
WAIT 0
CLEO_CALL label 0 (var1)(var2)
PRINT_FORMATTED_NOW "%i" 1000 var2
GOTO main_loop
}
SCRIPT_END
{
LVAR_INT in_var
label:
in_var = 13
CLEO_RETURN 0 (in_var)
}
Ou seja, eu enviei o valor da
var1
, mas pedi para que quando retornasse, o novo valor caísse na
var2
. Ou seja, a
var1
continuou 12, mas a
var2
agora tem o valor
13
, pois eu retornei o valor nela, e não na
var1
como antes.
Você tem que entender que isso é muito dinâmico e você pode enviar e retornar quantas variáveis for necessário!
Dê uma olhada neste exemplo:
Código: Selecionar tudo
SCRIPT_START
{
LVAR_INT var1
var1 = 12
main_loop:
WAIT 0
CLEO_CALL return_13 0 ()(var1)
PRINT_FORMATTED_NOW "%i" 1 var1
GOTO main_loop
}
SCRIPT_END
{
return_13:
CLEO_RETURN 0 (13)
}
Sim, a variável
var1
terá agora o valor
13
e mostrará
13
na tela!
Tudo o que o script acima fez foi retornar o valor
13
para a variável de retorno. E qual a variável de retorno?
var1
!
Perceba que não enviei nenhuma variável para a função (pra quê?), eu somente retornei. Como "simbolismo" eu deixei um
()
na chamada para indicar que eu não enviei variáveis.
Isto é somente visual, faz nada.
Agora eu criei uma função que: Pega uma variável, e um valor, e usa esse valor para somar naquela variável, e retorna o valor dela:
Código: Selecionar tudo
SCRIPT_START
{
LVAR_INT var1
var1 = 12
main_loop:
WAIT 0
CLEO_CALL add 0 (var1, 10)(var1)
PRINT_FORMATTED_NOW "%i" 1 var1
GOTO main_loop
}
SCRIPT_END
{
LVAR_INT in_var
LVAR_INT value
add:
in_var += value
CLEO_RETURN 0 (in_var)
}
O resultado vai ser a variável
var1
aumentando de 10 em 10 sem parar (lembre-se que estamos dentro de um loop!)
Dentro do
CLEO_CALL
, a
in_var
vai ter o valor atual da
var1
lá fora. Já a variável
value
vai ter o valor
10
(que é o valor que você enviou pra ela!).
Você já deve ter entendido mas eu quero te dar esta visualização:
CLEO_CALL
é muitíssimo útil para fazer operações mais complexas (ou repetitivas) onde você envia argumentos, e ele faz e/ou retorna alguma coisa para você.
Veja só este exemplo útil:
Código: Selecionar tudo
SCRIPT_START
{
LVAR_INT scplayer
LVAR_FLOAT x y z
GET_PLAYER_CHAR 0 scplayer
main_loop:
WAIT 0
IF IS_KEY_PRESSED VK_KEY_Y
CLEO_CALL get_closest_road 0 (scplayer) (x y z)
PRINT_FORMATTED_NOW "A coord da rua mais proxima eh: %.3f %.3f %.3f" 1 (x y z)
DRAW_CORONA (x y z) (1.0) (CORONATYPE_SHINYSTAR, FLARETYPE_NONE) (255 0 0)
ENDIF
GOTO main_loop
}
SCRIPT_END
{
LVAR_INT char // In
LVAR_FLOAT char_x char_y char_z
LVAR_FLOAT node_x node_y node_z
get_closest_road:
GET_CHAR_COORDINATES char (char_x char_y char_z)
GET_CLOSEST_CAR_NODE (char_x char_y char_z) (node_x node_y node_z)
CLEO_RETURN 0 (node_x node_y node_z)
}
Este
CLEO_CALL
tem a função de: Pegar a coordenada de um char (você pode usar qualquer char, não precisa ser o CJ), e com ela, pegar a coordenada do node (path) de carros mais próximo (leia isto como "pegar a coordenada da rua mais próxima"). O valor retornado será a coordenada
x y z
daquele node (path) mais próximo, ou seja, da rua mais próxima.
Ao retornar, eu usei esta coordenada para mostrar o valor dela na tela, e criar uma luz (corona) vermelha lá.
Eu gosto de imaginar
CLEO_CALL
como
comandos personalizados
. Sabe quando você quer um comando que não existe, mas é possível você mesmo fazer este comando? Então. Exatamente como funções de outras programações.
Inclusive, isso é facilmente compartilhável com as pessoas, e este é o principal motivo da categoria "
Scripts > Utilidades" existir aqui no fórum: Lá você geralmente encontra
CLEO_CALL
s úteis para o seu script.
Hoje mesmo fiz uma função que posso compartilhar aqui:
Código: Selecionar tudo
{
LVAR_INT hChar // In
LVAR_INT iTempVar
GetCharPedStatsID:
GET_PED_POINTER hChar iTempVar //CPed
iTempVar += 0x59C //m_pStat
READ_MEMORY iTempVar 4 FALSE iTempVar //CPedStats (+0x59C)
READ_MEMORY iTempVar 4 FALSE iTempVar //nNum (+0x0)
CLEO_RETURN 0 iTempVar
}
Simplesmente adicione isso em algum lugar do seu script (como os exemplos acima) e use a seguinte chamada:
A variável
iPedStatsID
terá o número ID do "ped stat" (do
data\pedstats.dat
) da pessoa.
Você nem precisa entender como que esta função funciona (requer entendimento sobre manipulação de memória que você aprenderá logo aqui no tutorial), tudo o que você precisa fazer é adicionar isto no seu script e usar.
Veja este script completo que pega uma pessoa próxima, usa esta função para pegar o ped stat dela, e checa e mostra na tela uma mensagem caso seja um policial, da sua gangue, ou uma puta.
Código: Selecionar tudo
SCRIPT_START
{
LVAR_INT scplayer
LVAR_INT char vehicle
LVAR_INT iPedStatsNum
CONST_INT STAT_PLAYER 0
CONST_INT STAT_COP 1
CONST_INT STAT_MEDIC 2
CONST_INT STAT_FIREMAN 3
CONST_INT STAT_GANG1 4
CONST_INT STAT_GANG2 5
CONST_INT STAT_GANG3 6
CONST_INT STAT_GANG4 7
CONST_INT STAT_GANG5 8
CONST_INT STAT_GANG6 9
CONST_INT STAT_GANG7 10
CONST_INT STAT_GANG8 11
CONST_INT STAT_GANG9 12
CONST_INT STAT_GANG10 13
CONST_INT STAT_STREET_GUY 14
CONST_INT STAT_SUIT_GUY 15
CONST_INT STAT_SENSIBLE_GUY 16
CONST_INT STAT_GEEK_GUY 17
CONST_INT STAT_OLD_GUY 18
CONST_INT STAT_TOUGH_GUY 19
CONST_INT STAT_STREET_GIRL 20
CONST_INT STAT_SUIT_GIRL 21
CONST_INT STAT_SENSIBLE_GIRL 22
CONST_INT STAT_GEEK_GIRL 23
CONST_INT STAT_OLD_GIRL 24
CONST_INT STAT_TOUGH_GIRL 25
CONST_INT STAT_TRAMP_MALE 26
CONST_INT STAT_TRAMP_FEMALE 27
CONST_INT STAT_TOURIST 28
CONST_INT STAT_PROSTITUTE 29
CONST_INT STAT_CRIMINAL 30
CONST_INT STAT_BUSKER 31
CONST_INT STAT_TAXIDRIVER 32
CONST_INT STAT_PSYCHO 33
CONST_INT STAT_STEWARD 34
CONST_INT STAT_SPORTSFAN 35
CONST_INT STAT_SHOPPER 36
CONST_INT STAT_OLDSHOPPER 37
CONST_INT STAT_BEACH_GUY 38
CONST_INT STAT_BEACH_GIRL 39
CONST_INT STAT_SKATER 40
CONST_INT STAT_STD_MISSION 41
CONST_INT STAT_COWARD 42
GET_PLAYER_CHAR 0 scplayer
main_loop:
WAIT 0
STORE_CLOSEST_ENTITIES scplayer (vehicle char)
IF DOES_CHAR_EXIST char
CLEO_CALL GetCharPedStatsID 0 (char)(iPedStatsNum)
IF iPedStatsNum = STAT_PROSTITUTE
PRINT_STRING_NOW "A pessoa proxima eh uma puta!" 1000
ENDIF
IF iPedStatsNum = STAT_COP
PRINT_STRING_NOW "A pessoa proxima eh um policial!" 1000
ENDIF
IF iPedStatsNum = STAT_GANG2
PRINT_STRING_NOW "A pessoa proxima eh da sua gangue!" 1000
ENDIF
ENDIF
GOTO main_loop
}
SCRIPT_END
{
LVAR_INT hChar // In
LVAR_INT p
GetCharPedStatsID:
GET_PED_POINTER hChar p //CPed
p += 0x59C //m_pStat
READ_MEMORY p 4 FALSE p //CPedStats (+0x59C)
READ_MEMORY p 4 FALSE p //nNum (+0x0)
CLEO_RETURN 0 p
}
Viu como é interessante e útil ter vários
CLEO_CALL
no seu script?
E sim, você também pode usar um
CLEO_CALL
dentro do outro, mas diferente do
GOSUB
, isso é infinito! (1024, para ser mais exato, o que é um número gigantesco). Ou seja, você pode fazer até umas táticas malucas de recursão que não terá problema.
Você sempre tem que ficar atento para quantas variáveis você envia e/ou retorna, ou o compilador dará erro (te alertará).
O
SCRIPT_END
está em cima (após fechar o escopo principal), mas também pode ficar embaixo de tudo (no fim de todo o código). Isto não importa, faça o que mais lhe agrada. Eu particularmente prefiro usar embaixo do escopo principal como nos exemplos acima.
Você pode tratar um
CLEO_CALL
como um código normal, usando
WAIT
etc, mas tome cuidado pois na maioria das vezes você só está chamando ele para fazer uma coisa e retornar outra, ele é mais útil para coisas instantâneas (a não ser que você esteja usando ele para realmente abrir novas portas para "novos" caminhos de script).
E sobre aquele
0
que nós nunca mexemos nele: Ele era usado na época do Sanny Builder, onde era necessário digitar o número de quantas variáveis (ou outros valores! Melhor dizendo: Quantos argumentos) você está enviando, ou retornando. Agora, no GTA3script esta contagem é automática, então tudo o que você tem que fazer é deixar
0
ali que o compilador fará o trabalho para você. Caso você mudar o
0
, terá um alerta de erro.
Deixando claro!
Para terminar, eu queria deixar claro que
GOSUB
e
CLEO_CALL
é sim muitíssimo utilizado em mods.
Sim, é muito utilizado sim.
Eu estou falando que sim, lembre-se.
Por exemplo, eu estou fazendo um mod de MP3 player e foi pressionado a tecla para ir para a próxima música: Eu usarei um
GOSUB
para descarregar a música atual, e outro
GOSUB
para chamar a próxima música. O script ficaria assim:
Código: Selecionar tudo
IF IS_KEY_PRESSED VK_KEY_K
GOSUB release_song
GOSUB load_next_song
GOSUB play
WHILE IS_KEY_PRESSED VK_KEY_K
WAIT 0
ENDWHILE
ENDIF
E todo o procedimento de descarregar uma música e tocar outra, estaria lá nas labels dos
GOSUB
s, enquanto no script principal eu teria somente isso acima, na qual deixa tudo muito mais organizado e confortável para ler, né?
E quando eu por exemplo desligar o MP3 player, eu vou ter que descarregar a música também, portanto eu usarei novamente o
GOSUB release_song
.
Outro exemplo foi num mod meu chamado
Ear Ringing (zumbido no ouvido) onde eu escolhi fazer o loop principal do mod funcionar totalmente por GOSUBs:
Código: Selecionar tudo
main_loop:
WAIT 0
GOSUB ProcessVolume
IF IS_PLAYER_PLAYING 0
GOSUB GetLastExplosionCoord
IF GOSUB IsNewExplosion
GOSUB StoreNewExplosion
IF GOSUB IsExplosionNearPlayer
GOSUB CalculateVolume
IF bIsAudioPlaying = FALSE
GOSUB PlayAudio
ENDIF
ENDIF
ENDIF
ENDIF
GOTO main_loop
Veja que a lógica do mod está aí, todo escrito por
GOSUB
s, de uma maneira muito fácil de ler e entender, enquanto o que cada
GOSUB
faz estão todos separados em outra parte do script, assim deixando mais limpo e não tirando o foco da parte principal do mod (o loop principal, onde está presente a lógica).
Veja o código fonte completo. Acredito que o código deste mod ficou bem organizado.
É esta a dica que eu dou, tire os "códigos feios" da sua frente, jogue-os em algum canto e use um
GOSUB
ou
CLEO_CALL
para chamá-los, faça com que a parte principal do seu código seja limpa, assim também caprichando no nome das labels.
O
CLEO_CALL
também é ótimo para ser usado várias vezes, mas só use caso você realmente achar necessário "ir para fora" do script principal. Tanto que nos exemplos que eu fiz aqui são simples e óbvios, o único exemplo realmente útil foi o do ped stats, o resto podia ser feito direto no código, ou em algum
GOSUB
! Mas para o tutorial ficar mais fácil, usei exemplos fáceis.
É comum você no início não encontrar tanta utilidade em
CLEO_CALL
, por ser mais útil em coisas mais complexas.
Note também que
CLEO_CALL
é um pouco pesado pro script, nada tão preocupante, mas tenha em mente que
GOSUB
nunca influencia no desempenho, enquanto um uso exagerado de
CLEO_CALL
sim.
Atualização: A partir da CLEO 4.4.3 (de 2023) é possível usar
IF
com
AND
ou
OR
(ou seja, múltiplas condições) para
CLEO_CALL
. Eu não sei se eu avisei este problema em outra parte do tutorial, então estou adicionando isto aqui. Antes havia problema em fazer isto, mas agora não há.
Um dos exemplos "simples" e úteis para o
CLEO_CALL
, também seria você, por exemplo, está criando vários peds (chars), então quando você cria um char você envia ele para um
CLEO_CALL
, onde neste
CLEO_CALL
teria comandos para "configurar" o seu char.
Seria mais ou menos assim:
Código: Selecionar tudo
LVAR_INT char // In
SetupChar:
SET_CHAR_HEALTH char 2000
SET_CHAR_WEAPON_SKILL char 2
SET_CHAR_MONEY char 1000
SET_CHAR_ACCURACY char 60
SET_CHAR_SHOOT_RATE char 80
CLEO_RETURN 0
Assim basta você criar o char e chamar a função acima desta maneira:
Código: Selecionar tudo
CREATE_CHAR PEDTYPE_GANG2 FAM1 0.0 0.0 0.0 (member1
CLEO_CALL SetupChar 0 (member1)
CREATE_CHAR PEDTYPE_GANG2 FAM1 0.0 0.0 0.0 (member2)
CLEO_CALL SetupChar 0 (member2)
Perceba que você não precisa retornar nada, afinal, você não está editando a variável, você só está aplicando comandos no char referenciado por aquela variável — se esta dúvida bateu pela sua cabeça, você esqueceu o que é um valor "handle"!
Viu como é divertido? É só liberar a sua mente.
Nota: Se ainda não viu, veja o
tutorial de como criar CHARs etc.
Próxima parte:
14. Indentação