- Joined
- Dec 31, 2005
- Messages
- 712
Eis o que eu queria: escrever aqui em português!
Vou traduzir o incrível tutorial que o Daelin escreveu, com o qual aprendi a usar JASS.
"Here's what I wanted: write here in Portuguese!
I'll translate the great tutorial Daelin wrote, with which I could learn JASS."
Tutorial de JASS
1. Introdução
Grande parte de vocês provavelmente ouviram falar de JASS e que é um modo eficiente de desenvolver Spells. Mas a verdadeira pergunta provavelmente é: o que exatamente é JASS e como aprender isso? Como muitas outras linguagens de programação, JASS é a linguagem desenvolvida pela Blizzard. Se você tem conhecimento básico em C++, Pascal ou qualquer outra linguagem que não seja orientada por objetos, este tutorial vai ser moleza. Senão, você terá algumas dificuldades, mas, enfim, você vai conseguir. Antes de continuar, sugiro que antes aprenda a trabalhar basicamente com as triggers. O tutorial vai ser baseado no conhecimento de triggers, então sem conhecê-las, vai ser difícil você aprender muita coisa.
Tente este tutorial sobre Trigger Enhanced Spells se você não consegue fazer este tipo de Spells: http://www.wc3sear.ch/viewtopic.php?t=13562
Nota: Você pode ter ouvido falar de Custom Script. É um nome alternativo para JASS.
Nota 2: Tenha este JASS Editor em mãos, para facilitar.
2. Por que JASS?
Simples, porque, primeiro, JASS avançado pode ajudá-lo a fazer Spells Multi-Instanceable. Para quem não sabe, Multi-Instanceable é a capacidade de uma Spell poder ser lançada por mais de uma unidade do mesmo jogador, ao mesmo tempo. Fazer isso é muito difícil com GUI (pra quem não sabe, GUI é o formato normal das triggers).
Outro uso do JASS é a velocidade. Uma vez que você aprender JASS, você pode fazer Spells muito mais rápido, e para ser honesto, são várias vezes mais eficientes. E outra coisa que eu notei sobre JASS é como obter incríveis efeitos como um suave Knockback. Acreditem, eu tentei com triggers, mas você nunca terá os grandes efeitos do que com JASS.
Onde você não deve usar JASS porque você não precisa dele em tudo: em campanhas quando você fizer Quests, cinematics e outras coisas, que podem ser facilmente feitas com triggers. Não precisa usar JASS porque às vezes pode dar bug.
Onde você deve usar JASS: eu recomendo totalmente em fazer Spells, se você quer fazer minigames legais em mapas e, claro, bons efeitos. Você tem mais liberdade e não é mais restrito às Ações repetitivas do FOR. Se ainda não é o bastante, apenas experimente e você não vai mais voltar!
3. Funções
Funções são partes de um programa (script) que executam um número limitado de Ações (podem ser feitas para executar um número ilimitado se usar um for infinito, mas o script não estará correto), uma depois da outra. Imagine-as como as Ações de uma função, as Ações de um "Pick Up Every Unit In Group and do Actions" etc. Para criar uma função, você deve usar esta estrutura:
E agora explicando. As palavras ‘function’ e ‘endfunction’ iniciam e fecham a função, respectivamente. No lugar de ‘nome’ você pode colocar o nome da função. Pode ser qualquer coisa desde que não deixe espaços. “Oz02” é válido, enquanto “Oz 02” não. Entretanto todas as funções devem ter um nome único. Se duas funções no mesmo mapa tiverem o mesmo nome, mesmo que em scripts diferentes, você terá um erro.
Nota: Comentários em JASS devem ter “//” no começo da linha. A linha inteira que inicia com “//” é ignorada pelo script.
Entre ‘function’ e ‘endfunction’ você pode colocar todas as Ações que quer que a função execute. Considere-as as Ações de uma trigger.
Nota: Você pode criar quantas funções quiser, mas não pode criar uma função dentro da outra. Exemplo:
Este script não sera válido. O correto seria...
4. Executando uma função
Funções devem ser executadas como triggers. Mas diferente delas, elas devem ser diretamente ‘chamadas’ e não com eventos. Considere-as como uma trigger desativada que pode ser executada apeas com
Para ‘chamar’, uma função, você deve usar:
Novamente, sendo ‘nome’ o nome de uma função.
Uma função pode chamar outra função, mas ela tem de aparecer no script ANTES de ser chamada.
Este script está incorreto:
Este script está correto:
Todas as Ações que você executar nas triggers são funções também. Elas são funções predefinicas e podem ser diretamente chamadas.
Por exemplo: a função “TriggerSleepAction” é o ‘wait’, mas vai ser explicado depois.
Nota: Suas funções não podem ter o mesmo nome de uma função predefinida. Use o JASS Editor para ver todas as funções.
5.Variáveis
Variáveis são lugares na memória com nomes, que podem armazenar um certo valor. Elas têm um nome como funções. Como em GUI, existem vários tipos de variáveis. As mais importantes são:
real – real em gui
boolean – boolean em gui
Existem dois tipos de variáveis:
a) Variáveis Locals: são declaradas em funções e só podem ser usadas naquela função. Use a seguinte estrutura:
‘Type’ é o tipo da variável e ‘nome’ é o nome dela. Você não pode repetir o nome de uma variável, desde que estejam em FUNÇÕES DIFERENTES. Exemplo:
Para se referir a locals, apenas escreva o nome delas.
CUIDADO: Locals só podem ser declaradas no começo da função, antes de qualquer outra linha de código. Este script está errado:
b)Variáveis Globals: as variáveis normais do GUI, criadas na janela das variáveis. Para se referir à elas, use “udg_nome”, sendo ‘nome’ o nome da global.
Nota: Locals e Globals podem ter o mesmo nome. Como as globals têm “udg” no começo, elas não serão confundidas pelas locals
Também há um tipo especial chamado handlers. Este tipo de variável existe e só podem ser usadas como locals. É especial porque pode armazenar qualquer tipo de variável. Pessoalmente, eu experimente com doodads, itens e unidades, mas também deve funcionar com a maioria dos tipos. Mas eu duvido que funcione com Integer e Real.
6.Estruturas Decisivas e Repetitivas
Vamos discutir um pouco sobre decisivas (if) e repetitivas (loop).
a) Estrutura Decisiva:
A estrutura decisiva em JASS é simples, como em C++:
Nota: O script não precisa de <>
Ok, a condição precisa ser uma comparação. Se essa comparação for verdadera, então “Ações then” serão executadas. Senão, “Ações else” serão executadas. Entretanto, não é obrigatório ter “else”. Se não colocá-lo, não coloque “Ações else”, também. Não esqueça do “endif” para fechar o if.
b) Estrutura Repetitiva
É a mesma estrutura em C++ ou Pascal. É indicada por “loop”, deste modo:
CUIDADO: Se você não fechar o loop com “endloop”, o WE vai fechar e você perderá todos os dados desde a última compilação que fez, antes de começar o loop. Então, certifique-se de que fechou o loop pra não deixar nada excapar
Não há muito para dizer: as Ações dentro do loop continuarão a ser executadas até que a condição de ‘exitwhen’ se torne verdadeira. Então as próximas Ações serão executadas.
7.Funções que usam/retornam um valor
Até agora nós conversamos apenas como planejar funções que não usam nem retornam nenhum valor. Essas funções raramente existem, são normalmente usadsa em triggers (sim, vamos usar as triggers em JASS, o qual chamarei como GUI pra não misturar).
Ok, eu disse que a estrutura de uma função começa com "function name takes nothing returns nothing". Na verdade, no lugar desses dois "nothings" você pode por uma ou mais variáveis (seguidos dos nomes em caso de variáveis usadas (taken) ). As variáveis depois do "takes" são valores usados pela função quando você a chama, enquanto as outras depois do "return" são os valores retornados pela função.
Nota: As funções só podem retornar um valor.
Vamos ver um exemplo de uma função que usa um valor. Esta função vai funcionar com algumas variáveis. Veja uma função predefinida (as Ações em GUI) e como se chama uma função que usa um valor:
Vamos analisar esse script. A função "start" chama a função "remove" e inclui dois parâmetros. Como você vê, os parâmetros estão entre parênteses e são separados com uma vírgula. O primeiro parâmetro de remove é uma global, e o segundo uma constante real.
Nota: Constantes são valores que não mudam de valor, não importa o que aconteça.
Vamos ver a primeira função "remove". Como você vê, os valores "cast" e "wait" não precisam mais ser declaradas como locals, porque todos os valores usados (vou chamar de valores 'taken') já são considerado locals. Agora veja as predefinidas.
Como eu disse, a função "TriggerSleepAction" é o "wait" no GUI, e faz a função esperar algum tempo antes de continuar. O valor entre parênteses (um parâmetro) é os segundos que ela vai esperar.
Nota: O tempo mínimo do Wait é 0.1 segundo. Qualquer valor menor que isso será definido para 0.1.
A próxima função "RemoveUnit" é, em GUI, "Unit – Remove Unit". Como em GUI ela remove a unidade nos parâmetros, que é a local "cast".
CUIDADO! Sempre que você chamar uma função que usa um valor você precisa colocar TODOS os parâmetros, e eles devem ser do mesmo tipo que as variáveis usadas na função chamada.
No caso de retornar um valor, o valor deve ser uma expressão lógica ou um integer. Exemplo:
Quando a função "start" começa, ela espera 0.1 segundo (porque valores abaixo de 0.1 são considerados 0.1) e então chama a função "condition". Como essa função usa um valor, a chamada precisa de um parâmetro.
A segunda função retorna um Boolean. Isso significa que vai retornar um valor Falso/Verdade. Ela usa a função IsUnitDeadBJ(cast) que verifica se uma unidade está morta. Se ela estiver morta, retorna Verdade. Senão, retorna Falso. Além disso, a função retorna o valor retornado (complexo? nem tanto) pela função IsUnitDeadBJ. Imagine que, depois que a função IsUnitDeadBJ resolve (isto é, verifica se está morta ou não), aparece o resultado no lugar dela (Falso ou Verdade), e que é retornado pela função "condition".
O "return" retorna o valor logo em seguida. Lembre-se de que o tipo do valor depois do "return" tem de ser do mesmo tipo especificado no começo da função (takes ... returns boolean).
function itself.
Observação: Quando uma função chega a um "return", ela pára de executar.
Nota: Você não vai e não deve chamar uma função antes de outras quando você já usou o valor retornado diretamente. Se você não usou o valor retornado, você pode usar a função.
CUIDADO!: Sempre que uma função tem de retornar um valor, tenha certeza de que ela retorna esse valor. Se você não fizer isso, WE fechará, e você chorará. Uma estranha situação é que, ainda que a função sempre retorne um valor, acontece um erro, como na situação a seguir:
Estranhamente, WE não vai considerar que um valor é retornado por estar dentro do IF. Ele vai fechar. Então, tenha certeza de que há um "return" fora de qualquer Loop, IF ou outras estruturas, para prevenir fechadas repentinas. IFs e Loops são realmente armadilhas!
8.Trabalhando com variáveis
Este capítulo é muito importante. Vamos aprender a como dar certo valor às variáveis. Vamos usar este exemplo:
O que ele faz? Usa uma variável local com o nome "cast". Agora podemos dar um valor a ela usando "set", um espaço, e então o nome da variável. Depois disso, teremos "=" e então o valor. Neste caso, nós demos o valor da função GetTriggerUnit(). Esta função é outra predefinida, e é o "Event Response – Triggering Unit" do GUI. Depois disso, removemos a unidade cast.
Eu escrevi as funções predefinidas deste tipo mais importantes que funcionam com variáveis unidades.
Nota: GetSpellTargetUnit(), GetOrderTargetUnit() e outras funções não retornam valor algum depois de GetTriggerSleepAction(), não importa o quão pequeno ele é. A exceção é GetTriggerUnit(). Por isso que eu digo para usar uma variável para cada unidade que vai usar.
9.Triggers
Ok, até agora não trabalhamos com os verdadeiros scrips de JASS, mas somente com pequenas partes. Se você fosse tentar esses códigos, provavelmente teria milhares de erros, ou pelo menos não funcionariam. Por isso que suas funções devem antes ser feitas por alguma coisa. Então, as triggers existem em JASS, como eu disse antes. Existem dois tipos de triggers: as Globals (as que vemos em GUI) e as Locals. Vamos falar primeiro das Globals, e, claro, como criar o seu primeiro script JASS.
a) Triggers Globals
Primeira coisa: crie uma trigger em GUI. Dê como evento "A Unit Starts the Effect of na Ability" e use a condição "Ability Being Cast equal to Sleep".
Agora, veja como JASS pode ser simples. Vá para "Edit > Convert to Custom Text" e clique em OK. O script vai aparecer à direita. Incrível não? Vamos dar uma olhada…
Perdeu a vontade? Não entendeu porcaria nenhuma? Então vamos analisar juntos. Ela começa com uma função condicional, que retorna um boolean. Ela usa a função GetSpellAbilityId(), que pega o rawcode da habilidade sendo usada (vá para o Object Editor e aperte Ctrl + D). GetSpellAbilityId() retorna um rawcode, então imagine um rawcode no lugar dela. Agora, se o rawcode for igual a "Ausl" a função vai retornar um valor falso e vai pular as outras Ações. Não parece certo, parece? Mas olhe o "not" na frente da igualdade. Na verdade, ele nega o valor da igualdade. Então, se a igualdade der verdade, a condição vai retornar falso. Não é muito eficiente, mas depois eu vou ensinar como fazer isso muito mais simples. A Blizzard gosta de complicar.
Voltando à função, se ela não retornar o valor falso, a função vai continuar, sair do IF e retornar um valor verdade.
A segunda função representa as Ações da sua trigger. Até agora, ela está vazia porque você não colocou nenhuma Action. Vamos discutir sobre isso depois.
E agora, a função continua com um comentário estranho. Ele separa as funções da trigger Global. Olhe abaixo dele. A função seguinte é como uma trigger. Ela é ativada no começo do mapa. Ela tem de ter o memso nome da trigger. Mas não se preocupe com elas por enquanto.
Abaixo, você verá uma trigger Global sendo criada. Note que todas as triggers devem ser criadas antes de ser usadas. Antes disso, ela simplesmente existirão como variáveis, mas nunca serão usadas. O mesmo com timers.
Agora, temos três funções interessantes, que serão freqüentemente usadas. A primeira registra um evento. A segunda, as condições e a terceira uma Action. Elas são mais complicadas, então vamos explicar uma por uma. Antes disso, abra o JASS Editor.
I. Funções registrando eventos
É óbvio que existem tantos eventos, mas você vai aprender a trabalhar com elas. É muito simples. Todos os eventos começam com o seguinte sintaxe:
O sintaxe é seguido por diferentes palavras-chave. Para ver todos os eventos, digite no JASS Editor TriggerRegister. Tenha certeza de que o botão "funções" está pressionado. Para ser mais simples, se você não sabe qual evento é qual, faça uma trigger em GUI, coloque o evento que quer e então converta para JASS.
Nota: Você pode registrar quantos eventos quiser. Exemplo:
Nesta trigger, temos dois eventos: um que ativa quando uma unidade morre, e outra quando uma unidade começa o efeito de uma habilidade. Normalmente, todas as funções de eventos têm como primeiro parâmetro o nome da trigger, e depois depende do evento. Eu não posso dizer muito sobre esse capítulo, apenas experimente, convertendo GUI para JASS.
II. Funções registrando condições
Estas funções são muito mais fáceis de fazer e explicar. Nós já comentamos sobre elas no capítulo "Funções que usam/retornam um valor". Retorne à ela se ainda está com dúvidas.
Agora, a estrutura para adicionar uma condição é simplesmente uma:
No lugar de "trigger_name" você debe colocar o nome da trigger à qual esta condição pertence. E no lugar de "nome" você deve por o nome da função que vai checar uma certa condição.
CUIDADO: neste caso, a função que será a condição não pode ter nenhum parâmetro. Por isso que não se pode transferir variáveis locals de uma trigger para outra, pelo menos, não agora.
Nota: a condição DEVE retornar um Boolean. Senão, você terá um erro.
Novamente, você tem de experimentar antes de ter bom resultados. Eu vou só mostrar como obter condições eficientes, e não ineficientes (como as que a Blizzard usa). Vamos usar uma trigger mais elaborada, que ativa somente quando uma unidade lança certa habilidade.
Esta é bem simples. A primeira função retorna um valor que é retornado pela igualdade GetSpellAbilityId() == 'A000'. E sobre a segunda, ela cria uma trigger Gloval e adiciona o evento e a condição. Se você está tendo problemas, experimente.
CUIDADO: você só pode adicionar uma condição! Mas você pode manipular elas em uma função melhor!
III. Funções registrando Ações
Este é o capítulo mais curto e mais simples. Funciona como as condições, mas tem um sintaxe um pouco diferente:
Então, você não precisa mais do "Condition" na frente do nome da função. E é tudo. E agora vamos usar uma trigger mais complexa, que simplesmente mata a unidade quando é alvo de uma magia 'A000':
Nota: você só pode adicionar uma Action. Além disso, a função não pode usar nem retornar valor algum.
b) triggers Local
Existem trigger que só podem ser criadas e declaradas em funções. Deste modo, elas só podem usar certas unidades, e você pode fazê-las multi-instance (que pode ser usadas várias vezes ao mesmo tempo) usando locals. As triggers Locals funcionam simplesmente como Globals, mas com algumas pequenas diferentes.
Nada muito complexo. A segunda função tem uma trigger Local. Essa trigger ativa quando a Trigger Unit da função pára de usar uma magia. A Action da trigger Local é a função Remove, que remove a Triggering Unit dessa função, que é a Triggering Unit da segunda função (a que pára de usar a magia), porque a função TriggerRegisterUnitEvent(t, GetTriggerUnit(), EVENT_UNIT_SPELL_ENDCAST) usa GetTriggerUnit() como parâmetro.
10.Leaks
Uma desvantagem das variáveis (sejam unidades, doodads, itens, triggers ou qualquer coisa, que não sejam valores numéricos) é que elas "vazam" (chamamos isso de Leak). O que isso significa? Significa que mesmo depois de usadas, se não removidas propriamente da memória, elas vão continuar lá e então, consumindo seus recursos. Muitas triggers em JASS que leakam causam muito lag no mapa, o que nenhum programador quer. Então como que se pode evitar leaks?
a) Anular as variáveis depois de usadas.
Não é um título muito sugestivo, é? Ok, a idéia é tirar a variável da memória, deixando-a com o valor que tinha antes de ser declarada. Esse valor é chamado "null" e significa que a variável está vazia. Exemplo:
No começo da função, onde declaramos a variável pela sintaxe "local unit cast", a variável "cast" recebeu o valor "null". Isso significa que ela não ocupa nada na memória, a não ser seu valor. Entretanto, nós demos um valor à ela, e gardamos esse valor pelo rótulo: o nome da variável. Depois do Wait, nós removemos a unidade. Mas a memória ainda contém o valor, mesmo depois da unidade (seu valor) ser removida. E então, demos o valor inicial de novo: "null".
Eu não tenho certeza de que depois de remover a unidade, ela ainda vai leakar, mas uma coisa é clara: se você quer manipular o "Target Unit of Ability Being Cast", e maipular isso por entre os Waits, sua única opção são Locals (ou Globals, mas que brevemente estarão de fora, sempre que você ver JASS). Se você não quer remover a unidade Target Unit of Ability Being Cast, é óbvio que ela vai continuar na memória, armazenado pela variável. E então, o único modo de remover a variável da memória é anulando-a.
Nota: você não pode anular integers ou reals, mesmo que queira. Isso porque elas não leakam.
b) Destruindo timers, lightnings, effects e triggers
Em alguns casos, simplesmente anulando variáveis não é suficiente. É o caso dos timers, special effects, lightnings e triggers. No caso deles, você terá de destruí-los antes de poder removê-los. Por quê? Porque, por exemplo, triggers são mais que simples objetos. Elas ativam dependendo de certas Ações e então, não dependem deles mesmos como os objetos. Então como destruí-los?
Nota: você NÃO DEVE anular variáveis antes de destruir o que ela continha. Se você o fizer, você não poderá mais destruí-la. Exemplo:
Esta trigger remove qualquer unidade que comece o efeito de uma habilidade. Entretanto, normalmente, ela seria destruída depois de 10 segundos, fazendo-a não existir mais. Porém neste caso, você primeiro anulou a variável e ENTÃO destruiu a trigger. Por isso que a trigger não será realmente destruída, porque está destruir algo nulo. Ou seja, a variável que está destruindo não contém mais a trigger.
Para descobrir mais sobre leaks, veja este Tutorial. É o melhor sobre leaks que eu já vi.
11.Timers
Timers funcionam de algum modo como triggers, mas a única diferença é que eles executam as Actiosn depois de um período, não importam as circunstâncias. Além disso, eles podem executar tais Ações mais de uma vez, se forem timers repetitivos. E para fazer a coisa ainda melhor, timers não têm o limite de 0.1 segundos como a TriggerSleepAction). Eles podem ter até 0.01 de intervalo, por isso que você pode ter efeitos "suaves", como knockbacks. Veja o script abaixo. Pode parecer muito complexo, mas você deve entender o que acontece.
CUIDADO: Para que a spell funcione, siga estas instruções:
- Baseie sua spell em Cripple e tenha certeza de que é a única e primeira magia criada no seu mapa, então ela pode ter o rawcode 'A000'
- Tenha certeza de que o nome da trigger é "trigger". O nome é case sensitive (consideram maiúsculas e minúsculas)
- Tenha certeza de que tem as variáveis unidades Globals com os nomes (case sensitive) "cast" e "targ"
Uma análise é necessária. Você provavelmente entendeu a função condicional e a última, então vamos para a função das Ações, chamada "Start". Bem, usamos duas variáveis Globals e demos o valor do alvo e da Triggering Unit (que seria a que lançou a magia). Para fazer essa spell multi-instance (locals), precisamos estudar o capítulo Handlers, que agora é um tanto avançado.
Agora, temos um timer criado para expirar a cada 0.03 segundo, repetitivo (o "true"), e cada vez que expirar, executar a função Knockback. Podemos dizer que funciona simplesmente como uma trigger temporária, mas que funciona muito melhor, uma vez que é repetitiva. Então, temos um Wait de 2 segundos, então, praticamente, o timer expirará 66 vezes. Depois disso, destruímos o timer para ter certeza de que não vai mais expirar e executar a função, e então anulamos as duas Globals para economizar memória.
A função Knockback pode parecer esquisita, mas é muito lógica. Sempre que ela é executada, ela move a unidade-alvo instantaneamente (SetUnitPosition) usando Polar Offset (PolarProjectionBJ em JASS). Polar Offset também tem alguns parâmetros: o ponto central (posição da unidade que lança a magia), a distância entre o primeiro e o segundo ponto (no caso é a distância das duas unidades +20) e o ângulo entre os dois pontos (que é sempre o mesmo, porque o alvo só é empurrado para trás e então o ângulo não muda). Espero que tenha entendido!!
12. Pick Up Every Unit
Decidi fazer uma seção só para essa função, já que ela é cheia das pegadinhas, e só se você for esperto pode conseguir multi-instanceability. Daelin só descobriu isso depois, graças a Vexorian (imagine o quanto ele é bom nisso xD)
Desta vez, vamos usar uma variável GRUPO. Vamos usar o script seguinte. É um Sleep em área, que faz as unidades de uma área dormirem por Sleep. Antes de começarmos, sugiro que você crie um dummy, uma dummy-spell baseada em Silence com 0.01 segundo de duração e um Sleep para o dummy. Consideremos os seguintes rawcodes:
Vamos fazer o script. A trigger deve se chamar "masssleep" (CASE SENSITIVE)
Muito a explicar, mas provavelmente essa informação vai ajudá-los. Vamos direto às Ações. A função GetUnirsInRangeOfLocAll pega todas as unidades a uma certa distância de um ponto. Se você quer afetar somente inimigos, você teria de usar outra função, e por isso você teria perdido o ponto-alvo.
Agora, FirstOfGroup pega a primeira unidade em um grupo. Não importa qual é a primeira, já que no final, todas serão afetadas. Agora, porque que eu dei à variável "u" o valor de FirstOfGroup(g) fora e dentro do loop? Se eu tivesse definido isso apenas dentro do loop, a unidade seria nula (nada) quando nós teríamos entrado no loop. E como a condição "u == null" seria verdadeira, o loop seria pulado.
IsUnitEnemy verifica se uma certa unidade é inimiga de um jogador. GetOwningPlayer pega o dono de uma unidade, neste caso, Triggering Unit, armazenado numa variável. A função GroupRemoveUnit remove uma unidade de um grupo. Se eu não tivesse feito isso, toda vez o loop selecionaria a primeira unidade do grupo, que já estava definida como a variável "u". Então o loop estaria sempre selecionando "u" e guardando-a nela mesma. Se você remove-la do grupo, então outra "primeira unidade" apareceria no grupo. Tenha em mente que se não há unidades no grupo, "u" será nulo (null). Essa é o truque para fechar o loop quando "u" for nulo!
Agora, criando o dummy. CreateUnitAtLoc não o permite usar GetLastCreatedUnit() para que possa usa-la depois de criada. A função retorna um valor, mas não importa, desde que você poderia ter usado ela na forma de "call CreateUnitAtLoc", mas desta maneira, você não poderia ,mais "pegar" essa unidade. O ExpirationTimer assegura de que a unidade vai desaparecer depois de um tempo. Normalmente, isso seria feito antes para conseguir multi-instanceability. Neste caso, tenha em mente de que o dummy não poderá mais ser detectada depois que o loop repetir, pois outra unidade será criada e armazenada naquela variável (dumb).
Note que eu anulei todas as variáveis, exceto "u", porque quando você saiu do loop, ela já era nula.
13.Arrays
Arrays são iguais a qualquer outra linguagem de programação. O único problema é que JASS não tem mais do que arrays unidimensionais. Então você não pode ter matrizes, ao menos no bom e clássico modo. Vamos ver como elas são declaradas e usadas:
Então você as declara com o sintaxe "local", o tipo, "array" e o nome. E para usa-las, use o nome dela e então o parâmetro onde você guardou o valor. Exemplo:
14. Conclusões
Sugiro que experimente as funções predefinidas e JASS, já que só posso dar uma idéia do que "custom script" significa. É você quem tem que experimentar outras funções. Sempre que você não conseguir lembrar ou usar uma função, faça a trigger em GUI e converta-a para JASS. Até mesmo eu e Daelin (estou me comparando com ele hehe) fazemos isso, já que é impossível lembrar de tantas funções. Estou esperando por respostas e sugestões para melhorar este tutorial. O capítulo de Handlers será adicionado logo, logo, espero! Finalmente eu descobri o que são handlers, ainda que não veja o verdadeiro sentido de usá-las, exceto em algumas situações.
~Daelin
Traduzido por: Marcelo Hossomi
Thanks³ Daelin!
Vou traduzir o incrível tutorial que o Daelin escreveu, com o qual aprendi a usar JASS.
"Here's what I wanted: write here in Portuguese!
I'll translate the great tutorial Daelin wrote, with which I could learn JASS."
Tutorial de JASS
1. Introdução
Grande parte de vocês provavelmente ouviram falar de JASS e que é um modo eficiente de desenvolver Spells. Mas a verdadeira pergunta provavelmente é: o que exatamente é JASS e como aprender isso? Como muitas outras linguagens de programação, JASS é a linguagem desenvolvida pela Blizzard. Se você tem conhecimento básico em C++, Pascal ou qualquer outra linguagem que não seja orientada por objetos, este tutorial vai ser moleza. Senão, você terá algumas dificuldades, mas, enfim, você vai conseguir. Antes de continuar, sugiro que antes aprenda a trabalhar basicamente com as triggers. O tutorial vai ser baseado no conhecimento de triggers, então sem conhecê-las, vai ser difícil você aprender muita coisa.
Tente este tutorial sobre Trigger Enhanced Spells se você não consegue fazer este tipo de Spells: http://www.wc3sear.ch/viewtopic.php?t=13562
Nota: Você pode ter ouvido falar de Custom Script. É um nome alternativo para JASS.
Nota 2: Tenha este JASS Editor em mãos, para facilitar.
2. Por que JASS?
Simples, porque, primeiro, JASS avançado pode ajudá-lo a fazer Spells Multi-Instanceable. Para quem não sabe, Multi-Instanceable é a capacidade de uma Spell poder ser lançada por mais de uma unidade do mesmo jogador, ao mesmo tempo. Fazer isso é muito difícil com GUI (pra quem não sabe, GUI é o formato normal das triggers).
Outro uso do JASS é a velocidade. Uma vez que você aprender JASS, você pode fazer Spells muito mais rápido, e para ser honesto, são várias vezes mais eficientes. E outra coisa que eu notei sobre JASS é como obter incríveis efeitos como um suave Knockback. Acreditem, eu tentei com triggers, mas você nunca terá os grandes efeitos do que com JASS.
Onde você não deve usar JASS porque você não precisa dele em tudo: em campanhas quando você fizer Quests, cinematics e outras coisas, que podem ser facilmente feitas com triggers. Não precisa usar JASS porque às vezes pode dar bug.
Onde você deve usar JASS: eu recomendo totalmente em fazer Spells, se você quer fazer minigames legais em mapas e, claro, bons efeitos. Você tem mais liberdade e não é mais restrito às Ações repetitivas do FOR. Se ainda não é o bastante, apenas experimente e você não vai mais voltar!
3. Funções
Funções são partes de um programa (script) que executam um número limitado de Ações (podem ser feitas para executar um número ilimitado se usar um for infinito, mas o script não estará correto), uma depois da outra. Imagine-as como as Ações de uma função, as Ações de um "Pick Up Every Unit In Group and do Actions" etc. Para criar uma função, você deve usar esta estrutura:
JASS:
function takes nothing returns nothing
//Ações executadas pela função
endfunction
Nota: Comentários em JASS devem ter “//” no começo da linha. A linha inteira que inicia com “//” é ignorada pelo script.
Entre ‘function’ e ‘endfunction’ você pode colocar todas as Ações que quer que a função execute. Considere-as as Ações de uma trigger.
Nota: Você pode criar quantas funções quiser, mas não pode criar uma função dentro da outra. Exemplo:
JASS:
function sleep takes nothing returns nothing
function entangle takes nothing returns nothing
endfunction
endfunction
Este script não sera válido. O correto seria...
JASS:
function entangle takes nothing returns nothing
endfunction
function sleep takes nothing returns nothing
endfunction
4. Executando uma função
Funções devem ser executadas como triggers. Mas diferente delas, elas devem ser diretamente ‘chamadas’ e não com eventos. Considere-as como uma trigger desativada que pode ser executada apeas com
Há exceções que serão mencionadas depois.Run Trigger Ignoring Conditions
Para ‘chamar’, uma função, você deve usar:
JASS:
call nome
Uma função pode chamar outra função, mas ela tem de aparecer no script ANTES de ser chamada.
Este script está incorreto:
JASS:
function sleep takes nothing returns nothing
call entangle
endfunction
function entangle takes nothing returns nothing
endfunction
JASS:
function entangle takes nothing returns nothing
endfunction
function sleep takes nothing returns nothing
call entangle
endfunction
Todas as Ações que você executar nas triggers são funções também. Elas são funções predefinicas e podem ser diretamente chamadas.
Por exemplo: a função “TriggerSleepAction” é o ‘wait’, mas vai ser explicado depois.
Nota: Suas funções não podem ter o mesmo nome de uma função predefinida. Use o JASS Editor para ver todas as funções.
5.Variáveis
Variáveis são lugares na memória com nomes, que podem armazenar um certo valor. Elas têm um nome como funções. Como em GUI, existem vários tipos de variáveis. As mais importantes são:
integer – integer em guilightning – lightning em gui
location – point em gui
unit – unit em gui
effect – special effect em gui
item – item em gui
doodad – doodad em gui
timer – timer em gui
group – unit group em gui
trigger – as próprias triggers em gui (serão discutidas depois)
real – real em gui
boolean – boolean em gui
Existem dois tipos de variáveis:
a) Variáveis Locals: são declaradas em funções e só podem ser usadas naquela função. Use a seguinte estrutura:
JASS:
local type nome
JASS:
function sleep takes nothing returns nothing
local unit cast
endfunction
function entangle takes nothing returns nothing
local trigger cast
endfunction
function arrow takes nothing returns nothing
local unit cast
endfunction
CUIDADO: Locals só podem ser declaradas no começo da função, antes de qualquer outra linha de código. Este script está errado:
JASS:
function entangle takes nothing returns nothing
endfunction
function sleep takes nothing returns nothing
local trigger t
call entangle
local unit cast
endfunction
b)Variáveis Globals: as variáveis normais do GUI, criadas na janela das variáveis. Para se referir à elas, use “udg_nome”, sendo ‘nome’ o nome da global.
Nota: Locals e Globals podem ter o mesmo nome. Como as globals têm “udg” no começo, elas não serão confundidas pelas locals
Também há um tipo especial chamado handlers. Este tipo de variável existe e só podem ser usadas como locals. É especial porque pode armazenar qualquer tipo de variável. Pessoalmente, eu experimente com doodads, itens e unidades, mas também deve funcionar com a maioria dos tipos. Mas eu duvido que funcione com Integer e Real.
6.Estruturas Decisivas e Repetitivas
Vamos discutir um pouco sobre decisivas (if) e repetitivas (loop).
a) Estrutura Decisiva:
A estrutura decisiva em JASS é simples, como em C++:
JASS:
if <condition> then
//Ações then
else
//Ações else
endif
Ok, a condição precisa ser uma comparação. Se essa comparação for verdadera, então “Ações then” serão executadas. Senão, “Ações else” serão executadas. Entretanto, não é obrigatório ter “else”. Se não colocá-lo, não coloque “Ações else”, também. Não esqueça do “endif” para fechar o if.
b) Estrutura Repetitiva
É a mesma estrutura em C++ ou Pascal. É indicada por “loop”, deste modo:
JASS:
loop
exitwhen <condition>
//Ações
endloop
CUIDADO: Se você não fechar o loop com “endloop”, o WE vai fechar e você perderá todos os dados desde a última compilação que fez, antes de começar o loop. Então, certifique-se de que fechou o loop pra não deixar nada excapar
Não há muito para dizer: as Ações dentro do loop continuarão a ser executadas até que a condição de ‘exitwhen’ se torne verdadeira. Então as próximas Ações serão executadas.
7.Funções que usam/retornam um valor
Até agora nós conversamos apenas como planejar funções que não usam nem retornam nenhum valor. Essas funções raramente existem, são normalmente usadsa em triggers (sim, vamos usar as triggers em JASS, o qual chamarei como GUI pra não misturar).
Ok, eu disse que a estrutura de uma função começa com "function name takes nothing returns nothing". Na verdade, no lugar desses dois "nothings" você pode por uma ou mais variáveis (seguidos dos nomes em caso de variáveis usadas (taken) ). As variáveis depois do "takes" são valores usados pela função quando você a chama, enquanto as outras depois do "return" são os valores retornados pela função.
Nota: As funções só podem retornar um valor.
Vamos ver um exemplo de uma função que usa um valor. Esta função vai funcionar com algumas variáveis. Veja uma função predefinida (as Ações em GUI) e como se chama uma função que usa um valor:
JASS:
function remove takes unit cast, real wait returns nothing
call TriggerSleepAction(wait)
call RemoveUnit(cast)
endfunction
function start takes nothing returns nothing
call remove(udg_caster, 1.00)
endfunction
Vamos analisar esse script. A função "start" chama a função "remove" e inclui dois parâmetros. Como você vê, os parâmetros estão entre parênteses e são separados com uma vírgula. O primeiro parâmetro de remove é uma global, e o segundo uma constante real.
Nota: Constantes são valores que não mudam de valor, não importa o que aconteça.
Vamos ver a primeira função "remove". Como você vê, os valores "cast" e "wait" não precisam mais ser declaradas como locals, porque todos os valores usados (vou chamar de valores 'taken') já são considerado locals. Agora veja as predefinidas.
Como eu disse, a função "TriggerSleepAction" é o "wait" no GUI, e faz a função esperar algum tempo antes de continuar. O valor entre parênteses (um parâmetro) é os segundos que ela vai esperar.
Nota: O tempo mínimo do Wait é 0.1 segundo. Qualquer valor menor que isso será definido para 0.1.
A próxima função "RemoveUnit" é, em GUI, "Unit – Remove Unit". Como em GUI ela remove a unidade nos parâmetros, que é a local "cast".
CUIDADO! Sempre que você chamar uma função que usa um valor você precisa colocar TODOS os parâmetros, e eles devem ser do mesmo tipo que as variáveis usadas na função chamada.
No caso de retornar um valor, o valor deve ser uma expressão lógica ou um integer. Exemplo:
JASS:
function condition takes unit cast returns boolean
return IsUnitDeadBJ(cast)
endfunction
function start takes nothing returns nothing
call TriggerSleepAction(0.01)
if condition(udg_cast)==true
call RemoveUnit(udg_cast)
endif
endfunction
Quando a função "start" começa, ela espera 0.1 segundo (porque valores abaixo de 0.1 são considerados 0.1) e então chama a função "condition". Como essa função usa um valor, a chamada precisa de um parâmetro.
A segunda função retorna um Boolean. Isso significa que vai retornar um valor Falso/Verdade. Ela usa a função IsUnitDeadBJ(cast) que verifica se uma unidade está morta. Se ela estiver morta, retorna Verdade. Senão, retorna Falso. Além disso, a função retorna o valor retornado (complexo? nem tanto) pela função IsUnitDeadBJ. Imagine que, depois que a função IsUnitDeadBJ resolve (isto é, verifica se está morta ou não), aparece o resultado no lugar dela (Falso ou Verdade), e que é retornado pela função "condition".
O "return" retorna o valor logo em seguida. Lembre-se de que o tipo do valor depois do "return" tem de ser do mesmo tipo especificado no começo da função (takes ... returns boolean).
function itself.
Observação: Quando uma função chega a um "return", ela pára de executar.
Nota: Você não vai e não deve chamar uma função antes de outras quando você já usou o valor retornado diretamente. Se você não usou o valor retornado, você pode usar a função.
CUIDADO!: Sempre que uma função tem de retornar um valor, tenha certeza de que ela retorna esse valor. Se você não fizer isso, WE fechará, e você chorará. Uma estranha situação é que, ainda que a função sempre retorne um valor, acontece um erro, como na situação a seguir:
JASS:
function bool takes nothing returns boolean
if udg_a==true then
return true
else
return false
endif
endfunction
8.Trabalhando com variáveis
Este capítulo é muito importante. Vamos aprender a como dar certo valor às variáveis. Vamos usar este exemplo:
JASS:
function entangle takes nothing returns nothing
local unit cast
set cast = GetTriggerUnit()
call RemoveUnit(cast)
endfunction
Eu escrevi as funções predefinidas deste tipo mais importantes que funcionam com variáveis unidades.
GetTriggerUnit() = Triggering Unit
GetEventDamageSource() = Damage Source
GetLastCreatedUnit() = Last Created Unit
GetAttackedUnitBJ() = Attacked Unit
GetAttacker() = Attacking Unit
GetSpellAbilityUnit() = Casting Unit
GetDyingUnit() = Dying Unit
GetEnteringUnit() = Entering Unit
GetManipulatingUnit() = Hero Manipulating Unit
GetKillingUnitBJ() = Killing Unit
GetOrderedUnit() = Ordered Unit
GetSummonedUnit() = Summoned Unit
GetSummoningUnit() = Summoning Unit
GetSpellTargetUnit() = Target Unit of Ability Being Cast
GetOrderTargetUnit() = Target Unit of Issued Order
Nota: GetSpellTargetUnit(), GetOrderTargetUnit() e outras funções não retornam valor algum depois de GetTriggerSleepAction(), não importa o quão pequeno ele é. A exceção é GetTriggerUnit(). Por isso que eu digo para usar uma variável para cada unidade que vai usar.
9.Triggers
Ok, até agora não trabalhamos com os verdadeiros scrips de JASS, mas somente com pequenas partes. Se você fosse tentar esses códigos, provavelmente teria milhares de erros, ou pelo menos não funcionariam. Por isso que suas funções devem antes ser feitas por alguma coisa. Então, as triggers existem em JASS, como eu disse antes. Existem dois tipos de triggers: as Globals (as que vemos em GUI) e as Locals. Vamos falar primeiro das Globals, e, claro, como criar o seu primeiro script JASS.
a) Triggers Globals
Primeira coisa: crie uma trigger em GUI. Dê como evento "A Unit Starts the Effect of na Ability" e use a condição "Ability Being Cast equal to Sleep".
Agora, veja como JASS pode ser simples. Vá para "Edit > Convert to Custom Text" e clique em OK. O script vai aparecer à direita. Incrível não? Vamos dar uma olhada…
JASS:
function Trig_Untitled_Trigger_001_Conditions takes nothing returns boolean
if ( not ( GetSpellAbilityId() == 'AUsl' ) ) then
return false
endif
return true
endfunction
function Trig_Untitled_Trigger_001_Ações takes nothing returns nothing
endfunction
//===========================================================================
function InitTrig_Untitled_Trigger_001 takes nothing returns nothing
set gg_trg_Untitled_Trigger_001 = CreateTrigger( )
call TriggerRegisterAnyUnitEventBJ( gg_trg_Untitled_Trigger_001, EVENT_PLAYER_UNIT_SPELL_EFFECT )
call TriggerAddCondition( gg_trg_Untitled_Trigger_001, Condition( function Trig_Untitled_Trigger_001_Conditions ) )
call TriggerAddAction( gg_trg_Untitled_Trigger_001, function Trig_Untitled_Trigger_001_Ações )
endfunction
Voltando à função, se ela não retornar o valor falso, a função vai continuar, sair do IF e retornar um valor verdade.
A segunda função representa as Ações da sua trigger. Até agora, ela está vazia porque você não colocou nenhuma Action. Vamos discutir sobre isso depois.
E agora, a função continua com um comentário estranho. Ele separa as funções da trigger Global. Olhe abaixo dele. A função seguinte é como uma trigger. Ela é ativada no começo do mapa. Ela tem de ter o memso nome da trigger. Mas não se preocupe com elas por enquanto.
Abaixo, você verá uma trigger Global sendo criada. Note que todas as triggers devem ser criadas antes de ser usadas. Antes disso, ela simplesmente existirão como variáveis, mas nunca serão usadas. O mesmo com timers.
Agora, temos três funções interessantes, que serão freqüentemente usadas. A primeira registra um evento. A segunda, as condições e a terceira uma Action. Elas são mais complicadas, então vamos explicar uma por uma. Antes disso, abra o JASS Editor.
I. Funções registrando eventos
É óbvio que existem tantos eventos, mas você vai aprender a trabalhar com elas. É muito simples. Todos os eventos começam com o seguinte sintaxe:
.TriggerRegister
O sintaxe é seguido por diferentes palavras-chave. Para ver todos os eventos, digite no JASS Editor TriggerRegister. Tenha certeza de que o botão "funções" está pressionado. Para ser mais simples, se você não sabe qual evento é qual, faça uma trigger em GUI, coloque o evento que quer e então converta para JASS.
Nota: Você pode registrar quantos eventos quiser. Exemplo:
JASS:
function main takes nothing returns nothing
set gg_trg_t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ( gg_trg_t, EVENT_PLAYER_UNIT_DEATH )
call TriggerRegisterAnyUnitEventBJ( gg_trg_t, EVENT_PLAYER_UNIT_SPELL_EFFECT
II. Funções registrando condições
Estas funções são muito mais fáceis de fazer e explicar. Nós já comentamos sobre elas no capítulo "Funções que usam/retornam um valor". Retorne à ela se ainda está com dúvidas.
Agora, a estrutura para adicionar uma condição é simplesmente uma:
JASS:
call TriggerAddCondition(trigger_name, Condition (function nome))
No lugar de "trigger_name" você debe colocar o nome da trigger à qual esta condição pertence. E no lugar de "nome" você deve por o nome da função que vai checar uma certa condição.
CUIDADO: neste caso, a função que será a condição não pode ter nenhum parâmetro. Por isso que não se pode transferir variáveis locals de uma trigger para outra, pelo menos, não agora.
Nota: a condição DEVE retornar um Boolean. Senão, você terá um erro.
Novamente, você tem de experimentar antes de ter bom resultados. Eu vou só mostrar como obter condições eficientes, e não ineficientes (como as que a Blizzard usa). Vamos usar uma trigger mais elaborada, que ativa somente quando uma unidade lança certa habilidade.
JASS:
function Cond takes nothing returns boolean
return GetSpellAbilityId() == ‘A000’
endfunction
function trigger takes nothing returns nothing
set gg_trg_t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ( gg_trg_t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(gg_trg_t, Condition(function Cond))
endfunction
CUIDADO: você só pode adicionar uma condição! Mas você pode manipular elas em uma função melhor!
III. Funções registrando Ações
Este é o capítulo mais curto e mais simples. Funciona como as condições, mas tem um sintaxe um pouco diferente:
JASS:
call TriggerAddAction(trigger_nome, function nome)
JASS:
function Cond takes nothing returns boolean
return GetSpellAbilityId() == ‘A000’
endfunction
function Act takes nothing returns nothing
local unit targ
set targ = GetSpellTargetUnit()
call KillUnit(targ)
endfunction
function main takes nothing returns nothing
set gg_trg_t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(gg_trg_t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(gg_trg_t, Condition(function Cond))
call TriggerAddAction(gg_trg_t, function Act)
endfunction
Nota: você só pode adicionar uma Action. Além disso, a função não pode usar nem retornar valor algum.
b) triggers Local
Existem trigger que só podem ser criadas e declaradas em funções. Deste modo, elas só podem usar certas unidades, e você pode fazê-las multi-instance (que pode ser usadas várias vezes ao mesmo tempo) usando locals. As triggers Locals funcionam simplesmente como Globals, mas com algumas pequenas diferentes.
JASS:
function Remove takes nothing returns nothing
call RemoveUnit(GetTriggerUnit())
endfunction
function act takes nothing returns nothing
local trigger t
set t = CreateTrigger()
call TriggerRegisterUnitEvent(t, GetTriggerUnit(), EVENT_UNIT_SPELL_ENDCAST)
call TriggerAddAction(t, function Remove)
endfunction
10.Leaks
Uma desvantagem das variáveis (sejam unidades, doodads, itens, triggers ou qualquer coisa, que não sejam valores numéricos) é que elas "vazam" (chamamos isso de Leak). O que isso significa? Significa que mesmo depois de usadas, se não removidas propriamente da memória, elas vão continuar lá e então, consumindo seus recursos. Muitas triggers em JASS que leakam causam muito lag no mapa, o que nenhum programador quer. Então como que se pode evitar leaks?
a) Anular as variáveis depois de usadas.
Não é um título muito sugestivo, é? Ok, a idéia é tirar a variável da memória, deixando-a com o valor que tinha antes de ser declarada. Esse valor é chamado "null" e significa que a variável está vazia. Exemplo:
JASS:
function Trap takes nothing returns nothing
local unit cast
set cast = GetTriggerUnit()
call TriggerSleepAction(1.50)
call RemoveUnit(cast)
set cast = null
endfunction
Eu não tenho certeza de que depois de remover a unidade, ela ainda vai leakar, mas uma coisa é clara: se você quer manipular o "Target Unit of Ability Being Cast", e maipular isso por entre os Waits, sua única opção são Locals (ou Globals, mas que brevemente estarão de fora, sempre que você ver JASS). Se você não quer remover a unidade Target Unit of Ability Being Cast, é óbvio que ela vai continuar na memória, armazenado pela variável. E então, o único modo de remover a variável da memória é anulando-a.
Nota: você não pode anular integers ou reals, mesmo que queira. Isso porque elas não leakam.
b) Destruindo timers, lightnings, effects e triggers
Em alguns casos, simplesmente anulando variáveis não é suficiente. É o caso dos timers, special effects, lightnings e triggers. No caso deles, você terá de destruí-los antes de poder removê-los. Por quê? Porque, por exemplo, triggers são mais que simples objetos. Elas ativam dependendo de certas Ações e então, não dependem deles mesmos como os objetos. Então como destruí-los?
Eu posso dizer que, no caso dos timers, se eles são repetitivos e chamam uma certa função sempre que expiram, eles vão continuar repetindo e chamando tal função mesmo depois de terem as variáveis anuladas. No caso das triggers, elas vão sempre ocorrer mesmo que anule a variável. Special Effects e Lightnings causam lags se não são destruídos depois de usados. Tente usar uma magia com vários Special Effects. Use ela mais de uma vez mas deixe os efeitos sumirem antes de usar de novo. Note que, depois de algum tempo, o jogo começará a "lagar".Timers – call DestroyTimer(name)
Effects – call DestroyEffect(name)
Lightning – call DestroyLightning(name)
Triggers – call DestroyTrigger(name)
Nota: você NÃO DEVE anular variáveis antes de destruir o que ela continha. Se você o fizer, você não poderá mais destruí-la. Exemplo:
JASS:
function Remove takes nothing returns nothing
call RemoveUnit(GetTriggerUnit())
endfunction
function triggy takes nothing returns nothing
local trigger t
set t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddAction(t, function Remove)
call TriggerSleepAction(10.00)
set t = null
call DestroyTrigger(t)
endfunction
Para descobrir mais sobre leaks, veja este Tutorial. É o melhor sobre leaks que eu já vi.
11.Timers
Timers funcionam de algum modo como triggers, mas a única diferença é que eles executam as Actiosn depois de um período, não importam as circunstâncias. Além disso, eles podem executar tais Ações mais de uma vez, se forem timers repetitivos. E para fazer a coisa ainda melhor, timers não têm o limite de 0.1 segundos como a TriggerSleepAction). Eles podem ter até 0.01 de intervalo, por isso que você pode ter efeitos "suaves", como knockbacks. Veja o script abaixo. Pode parecer muito complexo, mas você deve entender o que acontece.
JASS:
function Cond takes nothing returns boolean
return GetSpellAbilityId() == ‘A000’
endfunction
function Knockback takes nothing returns nothing
call SetUnitPositionLoc(udg_targ, PolarProjectionBJ(GetUnitLoc(udg_cast), DistanceBetweenPoints(GetUnitLoc(udg_cast), GetUnitLoc(udg_targ))+10, AngleBetweenPoints(GetUnitLoc(udg_cast), GetUnitLoc(udg_targ))))
endfunction
function Start takes nothing returns nothing
local timer t
set udg_cast = GetTriggerUnit()
set udg_targ = GetSpellTargetUnit()
set t = CreateTimer()
call TimerStart(t,0.03,true,function Knockback)
call TriggerSleepAction(2.00)
call DestroyTimer(t)
set udg_cast=null
set udg_targ=null
set t = null
endfunction
function InitTrig_trigger takes nothing returns nothing
local trigger t
set t= CreateTrigger()
call TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(t, Condition(function Cond))
call TriggerAddAction(t, function Start)
endfunction
CUIDADO: Para que a spell funcione, siga estas instruções:
- Baseie sua spell em Cripple e tenha certeza de que é a única e primeira magia criada no seu mapa, então ela pode ter o rawcode 'A000'
- Tenha certeza de que o nome da trigger é "trigger". O nome é case sensitive (consideram maiúsculas e minúsculas)
- Tenha certeza de que tem as variáveis unidades Globals com os nomes (case sensitive) "cast" e "targ"
Uma análise é necessária. Você provavelmente entendeu a função condicional e a última, então vamos para a função das Ações, chamada "Start". Bem, usamos duas variáveis Globals e demos o valor do alvo e da Triggering Unit (que seria a que lançou a magia). Para fazer essa spell multi-instance (locals), precisamos estudar o capítulo Handlers, que agora é um tanto avançado.
Agora, temos um timer criado para expirar a cada 0.03 segundo, repetitivo (o "true"), e cada vez que expirar, executar a função Knockback. Podemos dizer que funciona simplesmente como uma trigger temporária, mas que funciona muito melhor, uma vez que é repetitiva. Então, temos um Wait de 2 segundos, então, praticamente, o timer expirará 66 vezes. Depois disso, destruímos o timer para ter certeza de que não vai mais expirar e executar a função, e então anulamos as duas Globals para economizar memória.
A função Knockback pode parecer esquisita, mas é muito lógica. Sempre que ela é executada, ela move a unidade-alvo instantaneamente (SetUnitPosition) usando Polar Offset (PolarProjectionBJ em JASS). Polar Offset também tem alguns parâmetros: o ponto central (posição da unidade que lança a magia), a distância entre o primeiro e o segundo ponto (no caso é a distância das duas unidades +20) e o ângulo entre os dois pontos (que é sempre o mesmo, porque o alvo só é empurrado para trás e então o ângulo não muda). Espero que tenha entendido!!
12. Pick Up Every Unit
Decidi fazer uma seção só para essa função, já que ela é cheia das pegadinhas, e só se você for esperto pode conseguir multi-instanceability. Daelin só descobriu isso depois, graças a Vexorian (imagine o quanto ele é bom nisso xD)
Desta vez, vamos usar uma variável GRUPO. Vamos usar o script seguinte. É um Sleep em área, que faz as unidades de uma área dormirem por Sleep. Antes de começarmos, sugiro que você crie um dummy, uma dummy-spell baseada em Silence com 0.01 segundo de duração e um Sleep para o dummy. Consideremos os seguintes rawcodes:
dummy – ‘h000’
sleep - ‘ A001’
mass sleep (silence) – ‘A000’
Vamos fazer o script. A trigger deve se chamar "masssleep" (CASE SENSITIVE)
JASS:
function Cond takes nothing returns boolean
return GetSpellAbilityId()==’A000’
endfunction
function Mass_Sleep takes nothing returns nothing
local group g
local unit u
local unit cast
local unit dumb
local location p
set cast = GetTriggerUnit()
set p = GetSpellTargetLoc()
set g = GetUnitsInRangeOfLocAll(800.00, p)
set u = FirstOfGroup(g)
loop
exitwhen u==null
set u = FirstOfGroup(g)
if IsUnitEnemy(u, GetOwningPlayer(cast))==true then
call GroupRemoveUnit(g,u)
set dumb = CreateUnitAtLoc(GetOwningPlayer(cast), ‘h000’, GetUnitLoc(u), 0.00)
call IssueTargetOrderBJ(dumb, “sleep”, u)
call UnitApplyTimedLifeBJ (1.50, ‘BTLF’, dumb)
set dumb = null
endif
endloop
set g = null
set u = null
set cast = null
set p = null
endfunction
function InitTrig_masssleep takes nothing returns nothing
local trigger t
set t=CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(t, Condition(function Cond))
call TriggerAddAction(t, function Mass_Sleep)
endfunction
Agora, FirstOfGroup pega a primeira unidade em um grupo. Não importa qual é a primeira, já que no final, todas serão afetadas. Agora, porque que eu dei à variável "u" o valor de FirstOfGroup(g) fora e dentro do loop? Se eu tivesse definido isso apenas dentro do loop, a unidade seria nula (nada) quando nós teríamos entrado no loop. E como a condição "u == null" seria verdadeira, o loop seria pulado.
IsUnitEnemy verifica se uma certa unidade é inimiga de um jogador. GetOwningPlayer pega o dono de uma unidade, neste caso, Triggering Unit, armazenado numa variável. A função GroupRemoveUnit remove uma unidade de um grupo. Se eu não tivesse feito isso, toda vez o loop selecionaria a primeira unidade do grupo, que já estava definida como a variável "u". Então o loop estaria sempre selecionando "u" e guardando-a nela mesma. Se você remove-la do grupo, então outra "primeira unidade" apareceria no grupo. Tenha em mente que se não há unidades no grupo, "u" será nulo (null). Essa é o truque para fechar o loop quando "u" for nulo!
Agora, criando o dummy. CreateUnitAtLoc não o permite usar GetLastCreatedUnit() para que possa usa-la depois de criada. A função retorna um valor, mas não importa, desde que você poderia ter usado ela na forma de "call CreateUnitAtLoc", mas desta maneira, você não poderia ,mais "pegar" essa unidade. O ExpirationTimer assegura de que a unidade vai desaparecer depois de um tempo. Normalmente, isso seria feito antes para conseguir multi-instanceability. Neste caso, tenha em mente de que o dummy não poderá mais ser detectada depois que o loop repetir, pois outra unidade será criada e armazenada naquela variável (dumb).
Note que eu anulei todas as variáveis, exceto "u", porque quando você saiu do loop, ela já era nula.
13.Arrays
Arrays são iguais a qualquer outra linguagem de programação. O único problema é que JASS não tem mais do que arrays unidimensionais. Então você não pode ter matrizes, ao menos no bom e clássico modo. Vamos ver como elas são declaradas e usadas:
JASS:
local unit array a
set a[0]=GetTriggerUnit()
set a[1]=GetDamageSource()
14. Conclusões
Sugiro que experimente as funções predefinidas e JASS, já que só posso dar uma idéia do que "custom script" significa. É você quem tem que experimentar outras funções. Sempre que você não conseguir lembrar ou usar uma função, faça a trigger em GUI e converta-a para JASS. Até mesmo eu e Daelin (estou me comparando com ele hehe) fazemos isso, já que é impossível lembrar de tantas funções. Estou esperando por respostas e sugestões para melhorar este tutorial. O capítulo de Handlers será adicionado logo, logo, espero! Finalmente eu descobri o que são handlers, ainda que não veja o verdadeiro sentido de usá-las, exceto em algumas situações.
~Daelin
Traduzido por: Marcelo Hossomi
Thanks³ Daelin!