2001-2019, v5.1
 
 
 
 
  JOGOS  NEVERWINTER NIGHTS TUTORIAIS APROFUNDAMENTOS #2 / SEÇÕES
 

Aprofundamento em Aurora Toolset #2 - Entendendo o NWscript
Escrito por Tripa.Seca.

 

Introdução
Opa, estou de volta com um segundo, e muito importante, artigo. Pelo meu tempo grande no fórum do Neverwinter Nights acabei percebendo que a maioria das dúvidas que o pessoal tinha, e continua tendo, quanto a fazer scripts no Aurora Toolset para seus módulos não eram dúvidas quanto a scripts difíceis de serem feitos. Eram dúvidas básicas, que qualquer pessoa com umas semanas mechendo com o nwscript era capaz de resolver. Então me perguntei daonde vinham tais dúvidas. E acho que a resposta é simples: as pessoas que querem se tornar novos builders não se preocupam em entender a lógica e a estrutura do script, muitas delas apenas pegam scripts prontos e não se dão ao trabalho de entender pelo menos o básico do funcionamento daquele script para que ela possa modificá-lo ao seu gosto.

Eu sei que isso acontece, e já sofri com isso quando comecei a programar no nwscript. Mas como eu já sabia lógica de programação, foi apenas preciso entender a estrutura de programação do nwscript, que deriva da linguagem C++.

Assim, esse artigo transcorrerá sobre cinco assuntos que qualquer um deve dominar quando começar a mexer com scripts: a Lógica e a Estrutura do NWScript; Eventos; Action, Set e Get; AssignCommand, DelayCommand e ActionDoCommand; Varáveis Locais e Persistentes.

 

1. A Lógica e a Estrutura do NWScript
Vamos começar respondendo duas perguntas: o que é lógica? o que é estrutura?
Lógica é 'pensar' o script. É dividir o script em passos que devem ser feitos para que o script atinja o resultado esperado. É como se fossemos traduzir a linguagem do nwscript para o 'português'. Com os exemplos adiantes ficará fácil de entender.

Estrutura é a organização de um script, a divisão dele.
Não entrarei em detalhes nesta parte, apenas vou explicar através de exemplos

Vamos lá. Começaremos com algo simples e imaginário. Queremos um 'script' que pegue a primeira fruta em uma sacola, e se for uma maçã a coloque no cesto A e se for outra fruta não faz nada. Pense em como poderíamos escrever esse script em 'português'. Ficaria mais ou menos assim:

1: temos um objeto sacola;
2: pegue a primeira fruta dentro da sacola;
3: se for uma maçã -> coloque na cesta A;
4: fim.

Simples, não é. Agora vamos aumentar a complexidade desse 'script'. Queremos que o script pegue a primeira fruta da sacola e cheque. Se for uma maçã, coloca no cesto A. Se for uma banana, coloca no cesto B. Se for outra fruta coloca no cesto C. Teremos mais ou menos isto:

1: temos um objeto sacola;
2: pegue a primeira fruta da sacola;
3: se for uma maçã -> coloque no cesto A;
4: se não for maçã, mas for banana -> coloque no cesto B;
5: se não fora maçã, nem banana -> coloque no cesto C;
6: fim

Isso é que chamamos Lógica de Programação. Essa lógica é universal, independe da linguagem que estamos trabalhando. Este é o primeiro passo para se produzir um script no nwscript: pense nele através dessa lógica.

O segundo passo é a estrutura de programação. Imagine se no exemplo anterior invertêssemos as linhas 2 e 3, o 'script' simplesmente não funcionaria. Dentro da estrutura precisamos aprender 3 coisas: declaração de variáveis, condicionais e loops.

Declaração de variáveis é aonde dizemos ao script com que 'objetos' estamos trabalhando dentro do script. No caso do exemplo anterior precisamos dizer ao script que estamos trabalhando com dois objetos: 'sacola' e 'fruta'. Dentro do nwscript temos vários tipos de variáveis, e não pretendo entrar em detalhes. Temos os seguintes tipos de variáveis:

object - diz ao script que estamos trabalhando com objetos.
int - diz ao script que estamos trabalhando com números inteiros.
float - diz ao script que estamos trabalhando com números decimais.
string - diz ao script que estamos trabalhando com letras.
location - diz ao script que estamos trabalhando com um local.

Existem outros tipos como effect e vector, menos relevantes no momento. O fato é que este é um dos passos mais importantes dentro do script. No exemplo anterior poderíamos ter algo como:

object oSacola;
object oFruta = PeguePrimeiroObjetoDentroDe(oSacola);

Veja que atribuímos um 'valor' para o objeto oFruta, ao dizermos que este objeto é o primeiro objeto que o script encontrar dentro do objeto oSacola. Isso ficará mais claro mais pra frente com exemplos reais de scripts usados no NwN.

Condicionais são os famosos 'se' e 'se não' que aparecem dentro da lógica de programação. Dentro do nwscript temos três comandos: if, else if e else. Estes comandos seguem a estrutura:

if(condiçãoA)
executa comando1;
else if(condiçãoB);
executa comando2;
else
executa comando3;

Que traduzidos para o 'português' seria algo como:

se condiçãoA for verdadeira, executa o comando1.
se a condiçãoA for falsa, mas a condiçãoB for verdadeira, executa o comando2.
se as duas condições forem falsas, executa o comando3.

O if também é encontrado muitas vezes sozinho dentro do script, ou acompanhado com o else. Temos três opções de comandos: if sozinho, if com else, if com else if e else.

Também podemos executar multiplos comandos, colocando-os entre chaves:

if(condição) {
executa comando1;
executa comando2;
executa comando3;
}

Dentro da condição podemos fazer as chamadas operações de comparação, junto com operações boleanas.

Operações de comparação são operações aonde comparamos dois 'valores', checando se eles são iguais (==), diferentes (!=), A maior que B (A > B), A menor que B (A < B), A maior ou igual a B (A >= B), A menor ou igual a B (A <= B).

Operações boleanas são operações aonde testamos a validade de mais de um comando ao mesmo tempo, usando os operadores 'e' (&&) e 'ou' (||).

Vamos incrementar um pouco mais nosso exemplo, dizendo que maçãs ou laranjas vão para o cesto A. Bananas verdes vão para o cesto B. Bananas maduras para o cesto C. O resto para o cesto D.

object oSacola;
object oFruta = PeguePrimeiroObjetoDentroDe(oSacola);

if(oFruta == MAÇÃ || oFruta == LARANJA)
coloque oFruta no cesto A;
else if(oFruta == BANANA && EstáMadura(oFruta) == FALSO)
coloque oFruta no cesto B;
else if(oFruta == BANANA && EstáMadura(oFruta) == VERDADEIRO)
coloque oFruta no cesto C;
else
coloque oFruta no cesto D;

Perceba a utilização da estrutura if, else if, else, dos operadores lógicos e dos operadores booleanos.

Vamos agora tratar dos loops. Loops são a causa de muitos erros em scripts, simplesmente porque podemos cair em loops que nunca terminam. Temos três comandos que executam loops: while, for e do while. Trataremos somente do while, porque ele é o mais usado dentro do nwscript, e o mais simples também. Um loop executa uma tarefa enquanto uma certa condição for verdadeira, seguindo a estrutura:

while(testa condição)
executa comando;

Muito simples esta estrutura. Aqui também funciona o uso de chaves para a execução de múltiplos comandos.

Continuando nosso exemplo das frutas, vamos finalizá-lo, dizendo agora para o script que queremos checar todas as frutas dentro da sacola. Nosso 'script' será mais ou menos isso:

object oSacola;
object oFruta = PeguePrimeiroObjetoDentroDe(oSacola);

while (oFruta != OBJETO_INVÁLIDO){

if(oFruta == MAÇÃ || oFruta == LARANJA)
coloque oFruta no cesto A;
else if(oFruta == BANANA && EstáMadura(oFruta) == FALSO)
coloque oFruta no cesto B;
else if(oFruta == BANANA && EstáMadura(oFruta) == VERDADEIRO)
coloque oFruta no cesto C;
else
coloque oFruta no cesto D;

oFruta = PeguePróximoObjetoDentroDe(oSacola);
}

Repare que nossa condição dentro do loop é que execute os comandos if...else enquanto o objeto oFruta for um objeto válido, ou seja, enquanto existir uma fruta dentro da sacola.

Para finalizar a primeira parte deste artigo, seguiremos com um exemplo real, passo-a-passo de um script do nwn. Queremos que quando um jogador entre em uma área, que nosso script conte quantas espadas longas o jogador possui no inventário e armazene esse número em uma variável, e se esse número for maior que 3 e menor que 10, armazene a palavra 'espada' em outra variável. Não se preocupe em entender os comandos, preste atenção somente na lógica e na estrutura.

void main(){

object oPC = GetEnteringObject(); //pega o objeto entrando na área e armazena na variável oPC
object oItem = GetFirstItemInInventory(oPC); //pega o primeiro item do inventário do oPC e armazena na variável oItem
int iNumEspadas = 0; //define que o player não possui espadas longas
string sPalavra; //aqui iremos guardar a palavra 'espada' se necessário

while (oItem != OBJECT_INVALID){ //enquanto oItem for um objeto válido
if(GetItemBaseType(oItem) == BASE_ITEM_LONGSWORD) //se o oItem for um item do tipo espada longa
iNumEspadas++; //incrementa a variável (soma 1 ao resultado anterior da variável)
oItem = GetNextItemInInventory(oPC); //pega o próximo item no inventário do oPC e o armazena na variável oItem
}

if(iNumEspadas > 3 && iNumEspadas < 10) //se o numero de espadas for maior que 3 e menor que 10
sPalavra = "espada"; //armazena a palavra 'espada' na variável sPalavra

}

Aí está nosso script, um exemplo dentro do nwscript. Reitero aqui que você deve apenas se preocupar em entender a lógica e estrutura dele. As barras duplas (//) servem para inserir pequenos comentários dentro dos scripts, para que você e outras pessoas possam entendê-lo futuramente. Recomendo que você sempre comente as passagens mais importantes de seu script.

 

2. Eventos
Eventos são coisas específicas que ocorrem dentro de um módulo que disparam um script. Assim, quando alguém dá 'Rest', por exemplo, o evento OnRest do módulo é disparado, e se houver um script um script atachado a este evento, este script irá ser executado. Bom não vou me estender muito nesta parte, explicando cada um dos inúmeros eventos existentes dentro de um módulo. Todos os componentes de um módulo possuem seus próprios eventos (módulo, áreas, triggers, encounters, creatures, placeables, etc).

Saber trabalhar com estes eventos é extremamente importante para a customização de seu módulo. É com estes eventos que podemos fazer sistema de rest, de death, de respawn, etc. Não é necessária um detalhamento do que cada evento faz, pois o próprio nome dele já indica isso. Assim, se você deseja ativar um script quando o jogador entra em determinada área, você irá usar o evento OnEnter da área. Repare que o evento OnEnter também existe para o Módulo e para Triggers. Não confundi-los é extremamente importante. Um script no OnEnter do módulo só irá ser executado quando um jogador entrar no módulo, enquanto o jogador permanecer no módulo o script não será mais executado. Preste muita atenção se você não está cometendo erros deste tipo.

Pra finalizar, uma dica que não pode ser deixada para trás. Minimize o máximo que puder a utilização do evento OnHeartBeat. Este evento é disparado a cada 6 segundos. Se você deixar o script dele muito pesado, este script irá demandar um processamento muito grande. A utilização de scripts grandes nos eventos do tipo OnHeartBeat são a maior causa do famoso 'lag' nos módulos (não só em servidores, módulos single player podem ficar extremamente lentos também).

 

3. Action, Set e Get
Vamos entrar agora no universo das funções do nwscript. A primeira coisa que precisamos distinguir são os 'tipos' de funções. Eu entendo que existem 3 tipos básicos de funções no nwscript, que são iniciadas por Action, Set e Get.

Primeiro vou falar um pouco dos tipos Set e Get, que são os mais fáceis de entender. As funções iniciadas por Set servem para mudar algo. Assim se você quiser mudar o clima da área você irá usar SetWeather, se você quer trancar uma porta ou placeable, você irá usar SetLocked, se quiser alterar o horário use SetTime, e assim por diante. As funções iniciadas por Get são usadas para retornar um valor. Assim se quiser saber se uma criatura é um jogador, você irá usar GetIsPC, que irá retornar TRUE ou FALSE. Se quiser saber a divindade do personagem, irá usar GetDeity, etc. Aqui cabe uma dica rápida: sempre que quiser saber o nível total do player use GetHitDice.

Agora as funções iniciadas por Action. Estas funções dizem a um objeto qualquer (criatura, porta, placeable, área, etc) que uma ação deve ser executada. Porém esta ação não será executada imediatamente. Se o objeto estiver executando outra ação no momento, e ação designada irá ficar 'em espera' até que ele acabe o que estava fazendo e execute a ação que você mandou. Perceba que quase todas as funções Action têm alguma função semelhante sem Action na frente. Assim qual seria a diferença entre ActionJumpToLocation e JumpToLocation? A primeira irá designar uma ação que o objeto terá que realizar, assim se o objeto estiver fazendo algo ele irá terminar a tarefa anterior para ser transportado para o local designado na função. Já a segunda função irá transportar o objeto automaticamente. Bom, mas se vocês prestarem atenção na sintaxe da função podem ver que dentro dela só podemos designar o local de destino, não podemos dizer qual o objeto que deverá ser transportado. Para esclarecer isso, entraremos no nosso próximo tópico.

 

4. AssignCommand, DelayCommand e ActionDoCommand
No tópico anterior estávamos discutindo como poderíamos designar o objeto que seria transportado com a função JumpToLocation. Para fazermos isso usamos a função AssignCommand. Essa função possui a seguinte sintaxe: AssignCommand(objeto, comando). Esta função designa um comando a ser executado por um objeto. Assim podemos dizer qual será o objeto que será transportado com a função JumpToLocation, usadao AssignCommand(objeto, JumpToLocation(local)). Essa função é uma das mais úteis dentro do nwscript, porém muitos scripters iniciantes a desconhecem totalmente.

Temos também a função DelayCommand. Esta função faz com que um comando seja executado após certo tempo, ótima para ser usada em scripts que precisam de coisas acontecendo em intervalos de tempo determinados. A sintaxe dela é mais ou menos assim: DelayCommand(tempo, comando). Observer que AssignCommand e DelayCommand podem ser usadas em conjunto, uma dentro da outra. Podemos usar DelayCommand(tempo, AssignCommand(objeto, comando)) ou AssignCommand(objeto, DelayCommand(tempo, comando)).

A última dessas funções é a menos usada nos scripts, mas pode ser importante para algumas situações. O que ela faz é transformar um comando comum (tipo SetWeather) em uma ação. Vamos dar um exemplo. Quando o player entra na área, criamos um script para que ele ande até o meio da área, e quando ele chegar lá queremos que comece a chover. Poderia dar um grande trabalho se quiséssemos fazer usando DelayCommand. Mas podemos simplesmente criar uma lista de ações por player, assim uma ação será executada quando a outra acabar. Podemos usar a ação ActionMoveToLocation, e depois transformar SetWeather em uma ação, usando ActionDoCommand.

Uma última coisa antes de terminarmos este tópico. Não se esqueça que o jogador pode cancelar ações (qualquer função com Action ou ActionDoCommand). NPCs ou objetos não podem, mas players podem. Cuidado com isso ao fazer scripts que faça com que players executem ações.

Vou dar um exemplo agora, encaixando os três últimos tópicos. Vamos supor que você queira um script que, quando um player entrar em um trigger, transporte o player para outra área. O script abaixo deverá ser colocado então no evento OnEnter do trigger.

void main(){

object oPC = GetEnteringObject(); //armazena o objeto que está entrando no trigger na variável oPC
object oWP = GetObjectByTag("wp_destino"); //aqui será necessário criar um waypoint com a tag "wp_destino" no local de destino.

if(GetIsPC(oPC)==TRUE) //se o objeto que entrou no trigger é um player, e não um npc
AssignCommand(oPC, JumpToObject(oWP));

}

Um exemplo simples que engloba alguns dos aspectos abordados até aqui.

 

5. Variáveis Locais e Persistentes
Aqui temos uma série de funções iniciadas por Set e Get destinadas a armazenar certas variáveis. Este tipo de função é usada para transportar variáveis de um script para outro. A diferença entre variáveis locais (funções iniciadas por SetLocal e GetLocal) e variáveis persistentes (funções iniciadas por SetCampaign e GetCampaign), é que o primeiro tipo fica armazenado só enquanto o módulo estiver 'ligado', quando o módulo for desativado todas as variáveis se apagam, o segundo tipo guarda as variáveis em um banco de dados externo sendo possível recuperar as variáveis mesmo que o módulo tenha sido desativado.

Usar variáveis locais é extremamente importante, principalmente em quests, para dizer se algo foi cumprido ou não. Variáveis locais são o único meio de levar uma variável de um script para outro.

Variáveis persistentes são usadas quando estamos fazendo uma campanha e queremos levar informações de um módulo para outro, ou quando estamos trabalhando com um Mundo Persistente (um tipo servidor multiplayer) e queremos que as informações nunca se percam.

--

Este artigo ficou maior do que eu esperava, mas acho que consegui abordar aspectos muito importantes, que 'travam' novos scripters (e mesmo scripters mais experientes) na hora de fazer um script próprio.

 
 
  NEVERWINTER NIGHTS
2.8.1: APRESENTAÇÃO
2.8.2: PERSONAGEM
2.8.3: RAÇAS
2.8.4: CLASSES
2.8.5: DOMÍNIOS
2.8.6: NPCS
2.8.7: TALENTOS
2.8.8: PERÍCIAS
2.8.9: TUTORIAIS
2.8.9.1: PORTRAITS PERSONALIZADOS
2.8.9.2: ACRESCENTANDO SOM WAVE
2.8.9.3: APROFUNDAMENTOS #1
2.8.9.4: APROFUNDAMENTOS #2
2.8.9.5: APROFUNDAMENTOS #3
2.8.9.6: CRIANDO SUAS PRÓPRIAS CRIATURAS
2.8.9.7: INTRODUÇÃO AO AURORA TOOLSET
2.8.9.8: INTRODUÇÃO AO PLOT WIZARD #1
2.8.9.9: INTRODUÇÃO AO PLOT WIZARD #2
2.8.9.10: INTRODUÇÃO AO PLOT WIZARD #3
2.8.9.11: MODIFICANDO A PELE DAS CRIATURAS
2.8.9.12: CONSTRUÇÃO DE MÓDULOS #1
2.8.9.13: CONSTRUÇÃO DE MÓDULOS #2
2.8.9.14: CONSTRUÇÃO DE MÓDULOS #3
2.8.9.15: CONSTRUÇÃO DE MÓDULOS #4
2.8.9.16: CONSTRUÇÃO DE MÓDULOS #5
2.8.9.17: CONSTRUÇÃO DE MÓDULOS #6
2.8.9.18: CONSTRUÇÃO DE MÓDULOS #7
2.8.9.19: CONSTRUÇÃO DE MÓDULOS #8
2.8.9.20: CONSTRUÇÃO DE MÓDULOS #9
2.8.9.21: DIÁLOGOS
2.8.10: DICAS
2.8.11: TABELAS
2.8.12: IMAGENS
2.8.13: WALKTHROUGH

SHADOWS OF UNDRENTIDE
2.8.14.1: APRESENTAÇÃO
2.8.14.2: CLASSES
2.8.14.3: NPCS
2.8.14.4: TALENTOS
2.8.14.5: IMAGENS
2.8.14.6: WALKTHROUGH

HORDES OF THE UNDERDARK
2.8.15.1: APRESENTAÇÃO
2.8.15.2: CLASSES
2.8.15.3: TALENTOS
2.8.15.4: IMAGENS
2.8.15.5: WALKTHROUGH
/ PUBLICIDADE