sexta-feira, 11 de julho de 2008

Primeiras experiências com o NWNX4

Oláááá terráqueos.

Nesse últimos dias consegui algum tempo para mexer no meu módulo e agora estou tratando da integração dele com o MySQL.
Meu primeiro passo foi tentar achar algum módulo com alguma funcionalidade já pronta. Acabei pegando o próprio módulo de exemplo que vem no NWNX4. Ele deu um erro ao tentar abrir a única área que ele ja possuía, mas o importante pra mim eram os scripts. Criei então duas áreas, uma com NPCs para testar algumas funcionalidades e outra com uma criatura hostil para testar o sistema de morte.
Com tudo armado, eu abri o script e_mod_load e adicionei os seguinte comandos:

if (!doesTableExist("TEXTOS")) {
SQLExecDirect("CREATE TABLE TEXTOS (" +
"PLAYER VARCHAR(64) NOT NULL DEFAULT '~'," +
"TAG VARCHAR(64) NOT NULL DEFAULT '~'," +
"NAME VARCHAR(64) NOT NULL DEFAULT '~'," +
"TEXTO TEXT," +
"EXPIRE INT(11) DEFAULT NULL," +
"PRIMARY KEY (PLAYER, TAG, NAME)" +
") ENGINE=MyISAM DEFAULT CHARSET=latin1;"
);
if (!doesTableExist("TEXTOS")) {
SetLocalString(oMod, "ERROR", "The MySQL plugin could not set up the table 'pwdata'. Please make sure that the mysql userid in xp_mysql.ini has sufficient permissions to create a new table.");
PrintString("e_mod_load: CREATE pwdata failed.");
return;
}

}

Ou seja, apenas copiei o código anterior que verificava a existencia das tabelas pwdata e pwobjdata e as criavam e as adaptei para verificar e criar a tabela TEXTO, que gravará um texto qualquer.

Rodei o módulo no NWNX4 e funcionou legal.

Meu próximo passo então era gravar um texto qualquer no banco de dados e recuperar. Tentei usar as funções que já vêem no nwnx_sql (GetPersistentString e SetPersistentString), mas não gravava nada. Eu então coloquei algumas mensagens de debug e encontrei o problema:



As funções do nwnx_sql servem apenas para as tabelas ja definidas (pwdata e pwobjdata) e assim criava um comando SQL todo esquisito, como é possível conferir na screenshot acima. A solução então era criar meu próprio script de funcionalidades para a base de dados. Criei então a sh_sql, inicialmente com apenas 2 funções: SetTexto e GetTexto:

//:://////////////////////////////////////////////////
//:: sh_sql
/*
Funções para o uso do NWNX4 de acordo com o esquema de
base de dados criado por mim.
*/
//:://////////////////////////////////////////////////
//:: Copyright (c) 2008 SubHeaven World
//:: Created By: SubHeaven
//:: Created On: 07/10/2008
//:://////////////////////////////////////////////////

#include "nwnx_sql"

/************************************/
/* Function prototypes */
/************************************/

//Gravar texto do jogador.
// oPC: O jogador que está gravando o texto;
// sTexto: O texto a ser gravado na base de dados.
void SetTexto(object oPC, string sTexto);

//Ler texto do jogador.
// oPC: O jogador que gravou o texto anteriormente.
string GetTexto(object oPC);

/************************************/
/* Implementation */
/************************************/

void SetTexto(object oPC, string sTexto)
{
string sPlayer;
string sChar;

if (GetIsPC(oPC))
{
//SendMessageToPC(oPC, "Its a PC");

sPlayer = SQLEncodeSpecialChars(GetPCPlayerName(oPC));
sChar = SQLEncodeSpecialChars(GetName(oPC));
sTexto = SQLEncodeSpecialChars(sTexto);

string sSQL = "SELECT TEXTO " +
"FROM TEXTOS " +
"WHERE PLAYER = '" + sPlayer + "' " +
"AND NAME = '" + sChar + "'";

//SendMessageToPC(oPC, sSQL);
SQLExecDirect(sSQL);

if (SQLFetch() == SQL_SUCCESS)
{
SendMessageToPC(oPC, "Exist TEXTO");
// row exists
sSQL = "UPDATE TEXTOS SET " +
"TEXTO ='" + sTexto + "', " +
"WHERE PLAYER = '" + sPlayer + "' " +
"AND NAME = '" + sChar + "'";

//SendMessageToPC(oPC, sSQL);
SQLExecDirect(sSQL);
}
else
{
SendMessageToPC(oPC, "Doesn't exists TEXTO");
// row doesn't exist
sSQL = "INSERT INTO TEXTOS (" +
"PLAYER, " +
"NAME, " +
"TEXTO" +
") VALUES ('" +
sPlayer + "', '" +
sChar + "', '" +
sTexto + "')";

//SendMessageToPC(oPC, sSQL);
SQLExecDirect(sSQL);
}
}
else
{
SendMessageToPC(oPC, "Its not a PC");
}
}

string GetTexto(object oPC)
{
string sPlayer;
string sChar;

if (GetIsPC(oPC))
{
sPlayer = SQLEncodeSpecialChars(GetPCPlayerName(oPC));
sChar = SQLEncodeSpecialChars(GetName(oPC));

string sSQL = "SELECT TEXTO "+
"FROM TEXTOS " +
"WHERE PLAYER = '" + sPlayer + "' " +
"AND NAME = '" + sChar + "'";

//SendMessageToPC(oPC, sSQL);
SQLExecDirect(sSQL);

if (SQLFetch() == SQL_SUCCESS)
return SQLGetData(1);
else
{
return "Nao existe texto gravado pra voce.";
}
}
else
{
return "";
SendMessageToPC(oPC, "Its not a PC");
}
}

Funcionou legal para verificar se tinha alguma informação:



Ainda tive um pequeno problema na hora de gravar o texto:



Mas foi fácil verificar que eu não fechava o ' do texto.



Para o modulo, ainda criei mais 3 scripts:

sh_escrevertexto, que utiliza a sh_sql para gravar o texto na base de dados.

#include "sh_sql"

void main()
{
object oPC = GetPCSpeaker();
string sMensagem = GetName(oPC) + " esteve aqui.";

//SendMessageToPC(oPC, "sh_escrevertexto");

SetTexto(oPC, sMensagem);
}

sh_lertexto, que utiliza a sh_sql para ler o texto na base de dados.

#include "sh_sql"

void main()
{
object oPC = GetPCSpeaker();

string sMensagem = GetTexto(oPC);
SendMessageToPC(oPC, sMensagem);
}

e um script para ligar no evento OnUse de um objeto para acionar uma conversa que permite as ações acima, a sh_conversar:

//:://////////////////////////////////////////////////
//:: sh_conversar
/*
Função usada para fazer um objeto começar uma conversa com o
jogador.
*/
//:://////////////////////////////////////////////////
//:: Copyright (c) 2008 SubHeaven World
//:: Created By: SubHeaven
//:: Created On: 07/10/2008
//:://////////////////////////////////////////////////

void main()
{
object oPC = GetLastUsedBy();
//SendMessageToPC(oPC, "sh_conversar");
ActionStartConversation(oPC, "", TRUE, FALSE, FALSE, TRUE);
}

Resultado na base de dados:



O módulo de teste está nesse link.

A conversa está aqui.

Próximos passos:

- Criar as funções que gravam a localização do jogador e que o teleportam de volta praquele local;
- Criar as funções de tratamento de Death e Respawn do jogador;
- Criar uma função que calcule o loot das criaturas baseado em dados armazenados no banco de dados;
- Criar a função que impede a criação de mais de um personagem com o mesmo nome.

terça-feira, 8 de julho de 2008

Bancos e cadeiras - Parte 1

Bom... Antes de fazer a integração do meu mapa inicial com o MySQL eu resolvi fazer mais alguns ajustes no mapa e criar bancos sentáveis para os jogadores.
Comecei com as funções do Malese Kish, pois ja tinha testado o módulo e ele é cheio de ótimas idéias. Porém, os bancos só tinham espaço para uma pessoa sentar. Ficava estranho, e fui pesquisar uma maneira de resolver isso.
Acabei encontrando e baixando as Sittable Chair do Pacha e dentro dele tinha um blueprint chamado SitBox. Eu então montei um grupo onde colocava um banco comum e tres SitBox dentro dele (O SitBox é invisível) e funcionou quase perfeitamente. O personagem sentava no ar e só depois era transportado para o banco. Nos scripts do Malese Kish isso não acontecia e, comparando os dois códigos, eu percebi que o Patcha usava os comandos ActionJumpToLocation e ActionPlayCustomAnimation enquanto que o Moloch usava apenas JumpToLocation e PlayCustomAnimation. Eu alterei as linhas e, como suspeitava, o problema foi resolvido. Alterei também o comando onde era calculado a direção pra onde o jogador ficaria depois de sentado, pois a versão do Patcha era bem estranho.

O evento OnUse do SitBox ficou assim:

//::///////////////////////////////////////////////
//:: OnUse: Sit
//:: pat_sitted
//:://////////////////////////////////////////////
/*
Simple script to make PCs sit on a placeable
*/
//:://////////////////////////////////////////////
//:: Created By: Patcha
//:: Created On: 2006-12-08
//:: v1.76 By: Patcha
//:: v1.73 On: 2007-06-15
//:: dates: aaaa-mm-gg
//:://////////////////////////////////////////////
//::Change: SubHeaven 07/05/2008 Changed the functions ActionJumpToLocation for
// JumpToLocation and ActionPlayCustomAnimation
// for PlayCustonAnimation to fix the where the
// PC sit in the air before be transported for
// the chair location.
//::Change: SubHeaven 07/05/2008 Changed the funcion GetNormalizedDirection() for
// only the GetFacing to fix bugs with the PC directions
// after sitting.


void ActionPlayCustomAnimation(object oObject, string sAnimationName, int nLooping, float fSpeed = 1.0f)
{
PlayCustomAnimation(oObject, sAnimationName, nLooping, fSpeed);
}

// float GetNormalizedDirection(float fDirection):
// * This script returns a direction normalized to the range 0.0 - 360.0
// * Copyright (c) 2002 Floodgate Entertainment
// * Created By: Naomi Novik
// * Created On: 11/08/2002
float GetNormalizedDirection(float fDirection)
{
float fNewDir = fDirection;
while (fNewDir >= 360.0) {
fNewDir -= 360.0;
}
while (fNewDir <= 0.0) {
fNewDir += 360.0;
}

return fNewDir;
}

void main()
{
object oChair = OBJECT_SELF;
object oSitter = GetLastUsedBy();
object oLastSitter = GetLocalObject(oChair, "lastsitted");
string sChair = GetTag(oChair);
string sAutofit = GetLocalString(oChair, "autofit");
int iHeading = GetLocalInt(oChair, "degree");
int iPC_size = GetLocalInt(oChair, "size");
//Assign the heading degrees
location lChair_o = GetLocation(oChair);

//Old code: Change #2
//location lChair = Location(GetArea(oChair), GetPositionFromLocation(lChair_o), GetNormalizedDirection(GetFacingFromLocation(lChair_o) + iHeading));
//New code : Change #2
location lChair = Location(GetArea(oChair), GetPositionFromLocation(lChair_o), GetFacing(oChair));
//End Change #2

//Check if seat is free
if(GetDistanceBetween(oLastSitter, oChair) == 0.0f && GetArea(oLastSitter) == GetArea(oChair))
{
SetLocalInt(oChair, "taken", 1);
SpeakOneLinerConversation("", OBJECT_INVALID, TALKVOLUME_WHISPER);
}
else //if seat is free...
{
SetLocalInt(oChair, "taken", 0);

//Check for Character Race with original Creature Size
switch (iPC_size)
{
case 0:
//Check for Character Race with original Creature Size
if( ((GetRacialType(oSitter) == RACIAL_TYPE_ELF) && (GetCreatureSize(oSitter) == CREATURE_SIZE_MEDIUM)) ||
((GetRacialType(oSitter) == RACIAL_TYPE_HALFELF) && (GetCreatureSize(oSitter) == CREATURE_SIZE_MEDIUM)) ||
((GetRacialType(oSitter) == RACIAL_TYPE_HALFORC) && (GetCreatureSize(oSitter) == CREATURE_SIZE_MEDIUM)) ||
((GetRacialType(oSitter) == RACIAL_TYPE_HUMAN) && (GetCreatureSize(oSitter) == CREATURE_SIZE_MEDIUM)) ||
((GetSubRace(oSitter) == RACIAL_SUBTYPE_AASIMAR) && (GetCreatureSize(oSitter) == CREATURE_SIZE_MEDIUM)) ||
((GetSubRace(oSitter) == RACIAL_SUBTYPE_TIEFLING) && (GetCreatureSize(oSitter) == CREATURE_SIZE_MEDIUM)))
{
if(GetIsObjectValid(oChair) && GetIsObjectValid(oSitter))
{
//Debug
//SendMessageToPC(GetFirstPC(), "Testando Sitting PC size 0");

//Old code: Change #1
//AssignCommand(oSitter, ActionJumpToLocation(lChair));
//AssignCommand(oSitter, ActionPlayCustomAnimation(oSitter, "sitidle", 1));
//NewCode: Change #1
AssignCommand(oSitter, JumpToLocation(lChair));
PlayCustomAnimation(oSitter,"sitidle",1);
//End Change #1
SetLocalObject(oChair, "lastsitted", oSitter);
}
}
else
{
if(sAutofit != "")
{
AssignCommand(oChair, SetIsDestroyable(TRUE, FALSE, FALSE));
AssignCommand(oSitter, DestroyObject(oChair));
oChair = CreateObject(OBJECT_TYPE_PLACEABLE, "pat_low_" + sAutofit, lChair_o, FALSE, sChair);
if(!(GetIsObjectValid(oChair)))
oChair = CreateObject(OBJECT_TYPE_PLACEABLE, "pat_low_stool01", lChair_o, FALSE, sChair);
//Old code: Change #1
//AssignCommand(oSitter, ActionJumpToLocation(lChair));
//AssignCommand(oSitter, ActionPlayCustomAnimation(oSitter, "sitidle", 1))
//NewCode: Change #1
AssignCommand(oSitter, JumpToLocation(lChair));
PlayCustomAnimation(oSitter,"sitidle",1);
//End Change #1;
SetLocalString(oChair, "autofit", sAutofit);
SetLocalInt(oChair, "degree", iHeading);
SetLocalInt(oChair, "size", 1);
SetLocalObject(oChair, "lastsitted", oSitter);
}
else
SpeakOneLinerConversation("", OBJECT_INVALID, TALKVOLUME_WHISPER);
}
break;

case 1:
//Check for Character Race with original Creature Size
if( ((GetRacialType(oSitter) == RACIAL_TYPE_DWARF) && (GetCreatureSize(oSitter) == CREATURE_SIZE_MEDIUM)) ||
((GetRacialType(oSitter) == RACIAL_TYPE_GNOME) && (GetCreatureSize(oSitter) == CREATURE_SIZE_SMALL)) ||
((GetRacialType(oSitter) == RACIAL_TYPE_HALFLING) && (GetCreatureSize(oSitter) == CREATURE_SIZE_SMALL)))
{
if(GetIsObjectValid(oChair) && GetIsObjectValid(oSitter))
{
//Old code: Change #1
//AssignCommand(oSitter, ActionJumpToLocation(lChair));
//AssignCommand(oSitter, ActionPlayCustomAnimation(oSitter, "sitidle", 1))
//NewCode: Change #1
AssignCommand(oSitter, JumpToLocation(lChair));
PlayCustomAnimation(oSitter,"sitidle",1);
//End Change #1;
AssignCommand(oSitter, ActionJumpToLocation(lChair));
PlayCustomAnimation(oSitter,"sitidle",1);
SetLocalObject(oChair, "lastsitted", oSitter);
}
}
else
{
if(sAutofit != "")
{
AssignCommand(oChair, SetIsDestroyable(TRUE, FALSE, FALSE));
AssignCommand(oSitter, DestroyObject(oChair));
oChair = CreateObject(OBJECT_TYPE_PLACEABLE, "pat_mid_" + sAutofit, lChair_o, FALSE, sChair);
if(!(GetIsObjectValid(oChair)))
oChair = CreateObject(OBJECT_TYPE_PLACEABLE, "pat_mid_stool01", lChair_o, FALSE, sChair);
//Old code: Change #1
//AssignCommand(oSitter, ActionJumpToLocation(lChair));
//AssignCommand(oSitter, ActionPlayCustomAnimation(oSitter, "sitidle", 1));
//NewCode: Change #1
AssignCommand(oSitter, JumpToLocation(lChair));
PlayCustomAnimation(oSitter,"sitidle",1);
//End Change #1
SetLocalString(oChair, "autofit", sAutofit);
SetLocalInt(oChair, "degree", iHeading);
SetLocalInt(oChair, "size", 0);
SetLocalObject(oChair, "lastsitted", oSitter);
}
else
SpeakOneLinerConversation("", OBJECT_INVALID, TALKVOLUME_WHISPER);
}
break;

default:
//Character with no original Race and/or Creature size
SpeakOneLinerConversation("", OBJECT_INVALID, TALKVOLUME_WHISPER);
break;
}
}
}

Por fim, faltava apenas um problema. O personagem atravessava o banco e isso parecia meio estranho. Eu então apenas adicionei um CollisionBox, mudei seu tamanho para 1;0,02;1 e posicionei no encosto do banco.

Se quiser, você pode baixar o Prefab aqui. Basta copiar o arquivo para a pasta Override e reiniciar o toolset. Ele aparecerá lá em BluePrints -> Prefabs.

Você vai notar que ele esta sem nome. Na verdade, o que era pra ser o nome está em tag, e o nome ficou em branco. Ainda não consegui resolver esse bug.

Agora uma ScreenShot de como ficou:





Aqui um erf apenas com o SitBox do Patcha ja com a nova script.

terça-feira, 1 de julho de 2008

Tela de Boas Vindas

Ontem andei mexendo com a criação de GUI atravéz dos arquivos XML. Parti de um tutorial que achei na vault. Tentei criar minha própria imagem para a janela de mensagem mas ficou muito tosca, ai andei fuçando nos arquivos do próprio NeverWinter e achei uma muito legal.

Aqui está o código do meu arquivo XML:












OnLeftClick=UIButton_Input_ScreenClose() >







Enfim, era pra aparecer aí em cima, mas o Blogspot não aceita nem a tag code e nem a tag blockcode para mostrá-los corretamente. Caso queira ver como ficou meu arquivo XML, pode baixá-lo aqui no 4Shared.

E aqui esta o script que criei e adicionei ao evento OnClientEnter do módulo:

//:://////////////////////////////////////////////////
//:: sh_m_cliententer
/*
Mostra uma mensagem de bem vindo ao jogador.

Será adicionado mais funcionalidades, como apresentação, tutorial e gravar isso
na base de dados pois essa mensagem deve aparecer apenas uma vez para cada personagem.
*/
//:://////////////////////////////////////////////////
//:: Copyright (c) 2008 SubHeaven World
//:: Created By: SubHeaven
//:: Created On: 06/30/2008
//:://////////////////////////////////////////////////

void main()
{
//SendMessageToPC(GetFirstPC(), "OnClientEnter");
object oPC = GetEnteringObject();
DisplayGuiScreen(oPC, "TESTE", FALSE, "bemvindo.xml");
}

Sem esquecer que precisa da imagem também (A imagem está em formato tga e não tem como fazer upload aqui, então ela pode ser baixada aqui na minha pasta no 4Shared).

E finalmente, uma ScreenShot de como ficou no jogo:

E finalmente a #&@#@ da criatura andou.

Olááááá terráqueos.

Pois bem, tive meu primeiro final de semana de férias. Porém foram dias de explorar as catacumbas esquecidas do meu kitinete e dar uma faxina lá.

Porém, consegui fazer o bendito monstro andar. O comando WalkWayPoint parece não fazer nada. Detodas as formas que o coloquei ele não fazia meu monstro andar pelos WayPoints, eu então experimentei o comando ActionRandomWalk e funcionou perfeitamente, mas eu não estava satisfeito, então criei um script para o evento OnHeartBeat da criatura para ela mover de um WayPoint para outro, andar aleatoriamente por um tempo, mover para o próximo WayPoint, andar aleatoriamente e assim seguir o ciclo.

Aqui está o cript que criei:

//:://////////////////////////////////////////////////
//:: sh_c_heartbeat
/*
Default OnHeartbeat script for NPCs.

This script causes NPCs to perform default animations
while not otherwise engaged.

This script duplicates the behavior of the default
script and just cleans up the code and removes
redundant conditional checks.

*/
//:://////////////////////////////////////////////////
//:: Copyright (c) 2002 Floodgate Entertainment
//:: Created By: Naomi Novik
//:: Created On: 12/22/2002
//:://////////////////////////////////////////////////
// ChazM 6/20/05 if is encounter crature no longer checked - ambient anims flag set on spawn instead.
// ChazM 1/6/06 modified call to WalkWayPoints()
// ChazM 3/6/06 fixed AI level exit condition bug
// ChazM 3/23/06 added DoDebug(); no functionality changes
// SubHeaven 06/28/2008 added custom waypoint control

#include "nw_i0_generic"
#include "ginc_debug"

void DoDebug(string sMessage)
{

//if ((GetTag(OBJECT_SELF) != "3030_dwarf") && (GetTag(OBJECT_SELF) != "c_cow"))
return;
PrettyDebug("NW_C2_DEFAULT1 " + GetName(OBJECT_SELF) + ": " + sMessage);
}

void main()
{
DoDebug("I'm haveing a heartbeat!!!");
// * if not runnning normal or better Ai then exit for performance reasons
if (GetAILevel() == AI_LEVEL_VERY_LOW)
{
DoDebug("goodness gracious - i have very low AI!");
//SendMessageToPC(GetFirstPC(), "goodness gracious - i have very low AI!");
return;
}

// Buff ourselves up right away if we should
if(GetSpawnInCondition(NW_FLAG_FAST_BUFF_ENEMY))
{
// This will return TRUE if an enemy was within 40.0 m
// and we buffed ourselves up instantly to respond --
// simulates a spellcaster with protections enabled
// already.
if(TalentAdvancedBuff(40.0))
{
// This is a one-shot deal
SetSpawnInCondition(NW_FLAG_FAST_BUFF_ENEMY, FALSE);

//SendMessageToPC(GetFirstPC(), "GetSpawnInCondition(NW_FLAG_FAST_BUFF_ENEMY) e TalentAdvancedBuff(40.0");

// This return means we skip sending the user-defined
// heartbeat signal in this one case.
return;
}
}


//PrettyDebug("Entering big if");
if(GetHasEffect(EFFECT_TYPE_SLEEP))
{
// If we're asleep and this is the result of sleeping
// at night, apply the floating 'z's visual effect
// every so often

if(GetSpawnInCondition(NW_FLAG_SLEEPING_AT_NIGHT))
{
effect eVis = EffectVisualEffect(VFX_IMP_SLEEP);
if(d10() > 6)
{
ApplyEffectToObject(DURATION_TYPE_INSTANT, eVis, OBJECT_SELF);
}
}
}
// If we have the 'constant' waypoints flag set, walk to the next
// waypoint.
else if ( GetWalkCondition(NW_WALK_FLAG_CONSTANT) )
{
DoDebug("Calling WalkWayPoints!");
//string sMensagem = "Calling WalkWayPoints!";
//SendMessageToPC(GetFirstPC(), sMensagem);
WalkWayPoints(TRUE, "heartbeat");
}

// Check to see if we should be playing default animations
// - make sure we don't have any current targets
else if ( !GetIsObjectValid(GetAttemptedAttackTarget())
&& !GetIsObjectValid(GetAttemptedSpellTarget())
// && !GetIsPostOrWalking())
&& !GetIsObjectValid(GetNearestSeenEnemy()))
{
DoDebug(" I'm gonna look at playing some animations.");
if (GetBehaviorState(NW_FLAG_BEHAVIOR_SPECIAL) || GetBehaviorState(NW_FLAG_BEHAVIOR_OMNIVORE) ||
GetBehaviorState(NW_FLAG_BEHAVIOR_HERBIVORE))
{
// This handles special attacking/fleeing behavior
// for omnivores & herbivores.
DoDebug("I'm gonna do something special!");
DetermineSpecialBehavior();
}
else if (!IsInConversation(OBJECT_SELF))
{
if (GetSpawnInCondition(NW_FLAG_AMBIENT_ANIMATIONS)
|| GetSpawnInCondition(NW_FLAG_AMBIENT_ANIMATIONS_AVIAN))
//|| GetIsEncounterCreature())
{
DoDebug("I'm gonna play some ambient animations!");
//SpawnScriptDebugger();
PlayMobileAmbientAnimations();
}
else if (GetSpawnInCondition(NW_FLAG_IMMOBILE_AMBIENT_ANIMATIONS))
{
DoDebug("I'm gonna play some immobile ambient animations!");
PlayImmobileAmbientAnimations();
}
else
{
DoDebug("I don't have any flags telling me to play animations.");
}
}
else
{
DoDebug("I must be in a conversation!");
}
}

// Send the user-defined event signal if specified
if(GetSpawnInCondition(NW_FLAG_HEARTBEAT_EVENT))
{
SignalEvent(OBJECT_SELF, EventUserDefined(EVENT_HEARTBEAT));
}

//Recuperar o valor do contador global
int iContador = GetLocalInt(GetModule(), "i_hbCounter");

//Se o valor do contador é divisível por 20...
if ((iContador % 20) == 0 || GetLocalInt(OBJECT_SELF, "iWalking") == 0)
{
//Se a criatura não está em combate...
if (!GetIsInCombat(OBJECT_SELF))
{
//Recuperar a posição atual da criatura, incrementá-lo e
//procurar um Waypoint com essa numeração
int iAtual = GetLocalInt(OBJECT_SELF, "CurrWP");
iAtual = iAtual + 1;

string sLocal;

if (iAtual < 10)
{
sLocal = "WP_" + GetTag(OBJECT_SELF) + "_0" + IntToString(iAtual);
}
else
{
sLocal = "WP_" + GetTag(OBJECT_SELF) + "_" + IntToString(iAtual);
}

SendMessageToPC(GetFirstPC(), sLocal);
object oLocal = GetWaypointByTag(sLocal);

//Se não existe esse Waypoint e não é o primeiro,
//então procurar o primeiro Waypoint
if (oLocal == OBJECT_INVALID && iAtual != 1)
{
iAtual = 1;
sLocal = "WP_" + GetTag(OBJECT_SELF) + "_0" + IntToString(iAtual);
SendMessageToPC(GetFirstPC(), sLocal);
oLocal = GetWaypointByTag(sLocal);
}

//Se o local procurado é válido então...
if (oLocal != OBJECT_INVALID)
{
SendMessageToPC(GetFirstPC(), "oLocal != OBJECT_INVALID");
//Carregar o Waypoint
location lLocal = GetLocation(oLocal);

//Parar o que está fazendo
ClearAllActions();
//Ir até o Waypoint
//ActionMoveToLocation(lLocal);
ActionMoveToObject(oLocal);
//Mover-se aleatoriamente próximo ao Waypoint
ActionRandomWalk();

//Atualizar a posição atual
SetLocalInt(OBJECT_SELF, "CurrWP", iAtual);
}
//Senão, andar aleatoriamente no local
else
{
SendMessageToPC(GetFirstPC(), "oLocal == OBJECT_INVALID");
ActionRandomWalk();
}
}

SetLocalInt(OBJECT_SELF, "iWalking", 1);
}

}

Claro que o que eu realmente criei está comentado em português. Mas enfim, eu criei um contador no OnHeartBeat do módulo, para definir quando o código no OnHeartBeat da criatura será executado, no código acima, a cada 20 HeartBeat do módulo, eu executo o código no OnHeartBeat da criatura e com isso não fico fazendo os mesmos comandos a cada 6 segundos. Eu também criei duas variáveis na criatura, uma para armazenar em qual WayPoint ela está (CurrWP) e outra para indicar que a criatura já esta em movimento (iWalking). A CurrWP grava o numero do WayPoint em que a criatura está no momento, quando a criatura é criada, ela começa com valor 1 e eu então crio a tag "WP_Tag_01" e vou modificando apenas esse numero final para indicar pra onde a criatura está indo. Quando não eu não mais encontrar uma tag na sequência (Digamos que tenha criado 5 WayPoints e o programa procure o WP_tag_06) ele volta para o 1.
Como esse evento apenas ocorre a cada 20 HeartBeat do módulo, a criatura ficava estática logo depois do seu respawn, eu criei então a variável iWalking que começa com valor 0 e assim, mesmo que não tenha passado os 20 HeartBeat, se a criatura ainda não esta andando, o script executa os comandos e muda o valor de iWalking para 1, indicando que a criatura agora está andando normalmente pelo mapa. Porém, quando ele anda para outro WayPoint e executa o comando ActionRandomWalk, a criatura volta para o primeiro WayPoint e só então começa a andar aleatoriamente. Estranho... Mas pelo menos não ficou como uma estátua como antes. Depois vou tentar descobrir como resolver isso.

E pra finalizar esse post, está o script do OnHeartBeat do módulo:

// sh_m_heartbeat
/*
Script para o evento OnHeartBeat do modulo.

*/
//:://////////////////////////////////////////////////
//:: Copyright (c) 2008 SubHeaven World
//:: Created By: SubHeaven
//:: Created On: 06/28/2008
//:://////////////////////////////////////////////////

void main()
{
//Inclementar o contador de heartbeat. Esse contador serve para evitar
//que rotinas ligadas ao evento heartbeat de outros objetos sejam executadas
//a cada 6 segundos.
int iModuleCont = GetLocalInt(GetModule(), "i_hbCounter");

if (iModuleCont == 100)
iModuleCont = 1;
else
iModuleCont = iModuleCont + 1;

SetLocalInt(GetModule(), "i_hbCounter", iModuleCont);

//SendMessageToPC(GetFirstPC(), "HeartBeat " + IntToString(iModuleCont));
}

quinta-feira, 26 de junho de 2008

Combate!!!!!

Mais algumas imagens de como está ficando.























A #@#$& do lobo não anda! >.<

Um pouco de mimimi...

Ando meio sem tempo. Tive aula durante o ultimo final de semana inteiro, literalmente. É o lado ruim de ser nerd.

Essa última semana eu tentei entrar em um grupo para montar um modulo básico para servir de template para outros builders. Como sempre, apareceram um monte de gente disposta a judar, cada pessoa com uma idéia diferente (Sempre diferentes das minhas idéias) e uma meia dúzia com seus mimimis de sempre.

Minha idéia era fazer um template de PW básico com scripts principais ja configurados e mapas já feitos. Assim cada pessoa que quisesse montar seu servidor poderia pegar essa base e apenas adicionar mais coisas.

Também queria focar em mapas maiores e com menos detalhes (Apenas uma pequena parte do mapa mais trabalhado apenas para as SS dos jogadores), pois sempre prefiro ter um load maior do que ficar fazendo load a cada 3 monstros.

Independente do que saia la no grupo (Os chorões vivem dizendo que ja tem isso ou aquilo pronto mas nunca deram o link pra baixar) eu vou continuar meu projeto solo. Minha idéia é a seguinte:

Criar um mapa inicial onde seja possível jogar do lvl 1 ao 4.

Colocar algumas quests nela para ajudar no desenvolvimento do personagem.

Ajustar os scripts básicos (Morte, Banco de Dados, Respawn e loot);

E só.

Tendo isso funcional, minha próxima meta será disponibilizar o módulo e encontrar alguém para hostear o jogo e testar, pois estou sem internet em casa e a internet depende de aumento de salário.

Tendo os scripts básicos funcionando, será mais fácil adicionar mais mapas, monstros e quests (Apesar de que os diálogos são igualmente cansativos de se fazer. São divertidos no começo, mas se tornam cansativos depois de milhares de linhas escritas e outras milhares ainda por escrever).

O importante, é conseguir criar esse mapa inicial e disponibiliza-lo pra comunidade, pois assim, mesmo que eu não tenha mais tempo, outras pessoas podem continuar de onde parei e não fazer como as dezenas de projetos que começaram e nunca sairam do papel, pois seus idelizadores resolveram viver suas vidas e não disponibilizaram o que ja tinham feito.

Então antes de disponibilizar o módulo, eu preciso ter as seguintes implementações:

- 1 Mapa inicial. [Vila dos Tropeiros quase pronta. Ainda faltam mais algumas implementações, terminar as laterais e consertar a ponte]

- 3 Interiores de casas (Taverna, Celeiro e uma casa de NPC). [Nada]

- 1 Dungeon (Dungeon das aranhas gigantes). [Nada]

- Script de Respawn. [Quase pronto. Falta apenas fazer a @#$&# do monstro andar depois do respawn]

- Script de Loot. [Pronto, mas falta bolar um jeito de limitar sem bloquear por completo a habilidade de pickpocket]

- Script de banco de dados. [Ja instalei o NWNX4 e o MySQL, mas agora falta fazer as implementações para salvar no banco]

- Script de morte. [É o que mais existe. Provavelmente usarei o sistema do Defiance IV e dicionarei mais coisas no futuro, como o sistema de Fuge Death do HCR2]

- Criaturas. [Dois Blueprints prontos: Cão Selvagem e Aranhas Marrons. Precisarei de mais uma variação das aranhas e um boss]

sexta-feira, 13 de junho de 2008

Meu primeiro Script (Precisa de melhoria. Muita melhoria)

Como prometido. Aqui esta meu primeiro script. Esta funcionando perfeitamente como eu era meu objetivo inicial, mas agora quero adicionar a capacidade de diminuir a chance de drop de item a medida que a diferença de level entre o jogador e a criatura for maior.

Em todo caso aí está:

//:: sh_calculardrop
/*
Calcule the loot of creature. You can change the drop rate creating and modifing a
variable called sh_iDropRate in the item variable of type float that can be between
0.00 to 100.00 that represent the chance drop of the item (More is better) and create
a variable named sh_mDropRate in the in the module set of type float tha can be any
positive number. If sh_iDropRate don't exists, I use 0 instead (No chance to drop), if
sh_mDropRate don't exists, I use 1 instead (No modification).
*/
//:://////////////////////////////////////////////////
//:: Copyright (c) 2008 SubHeaven World
//:: Created By: SubHeaven
//:: Created On: 06/12/2008
//:://////////////////////////////////////////////////

void CalcularDrop(object oCriatura)
{
//Vejo se a criatura é válida e se não é um jogador
if (oCriatura != OBJECT_INVALID && !GetIsPC(oCriatura))
{

//Recupero o modificador de chance de drop do modulo
float lfGlobalRate = GetLocalFloat(GetModule(), "sh_mDropRate");

//Pego o primeiro item do inventário da criatura
object oItem = GetFirstItemInInventory(oCriatura);

//Faço um loop no inventário da criatura
while (oItem != OBJECT_INVALID)
{
//Pego a chance de drop do item
float lfItemRate = GetLocalFloat(oItem, "sh_iDropRate");

//Calculo um numero aleatorio de 0 a 10000
float lfChance = IntToFloat(Random(10000)) / 100;


//Verifico se a chance de drop do item está dentro dos
//valores permitidos (entre 0 e 100%)
//Se não eu ajusto para um valor padrão
if (lfItemRate > 100.0)
{
lfItemRate = 100.0;
}
else if (lfItemRate < 0.0)
{
lfItemRate = 0.0;
}

//Aqui eu verifico se o valor aleatorio que consegui
//anteriormente é maior que a chance de drop do item
//contando com o ajuste do modulo
if ((lfItemRate * lfGlobalRate) < lfChance)
DestroyObject(oItem);

//Esse aqui são apenas algumas linhas de debug
//Ele mostram os valores aletorios conseguidos
//e as chances de drop de cada item
string sMensagem = "(" + FloatToString(lfChance) + ") DC = " + FloatToString(lfItemRate * lfGlobalRate);
SendMessageToPC(GetFirstPC(), sMensagem);
SendMessageToAllDMs(sMensagem);
PrintString(sMensagem);

//Terminado com um item eu parto pro próximo item
//do inventário
oItem = GetNextItemInInventory(oCriatura);
}
}
}

quarta-feira, 11 de junho de 2008

Eu e minhas férias

Oláá terráqueos!!!

Pois é. Pensei que já estaria em semi-férias essa semana, mas férias da faculdade mesmo só depois que os alunos que ficaram de 3ª VA (Uma éspécie de "recuperação") fizerem as provas deles. Antes disso vou ter que labutar por cada minuto extra.

Mas em todo caso, ontem comecei a juntar alguns scripts que peguei na Vault. Um dos que primeiro testei foi o script Dave's Simple Respawn. Apenas dois scripts, um para o evento OnDeath da criatura e outro para o evento OnRespawn. Criei a variável Global como indicado no readme e o respawn funcionou que é uma maravilha, porém o monstro não me atacava e nem andava depois do respawn. O problema de atacar foi fácil de resolver. Eu vi que os outros BluePrints de monstros já prontos ja tinham vários eventos setados neles (Todos os slots preenchidos), então apenas exportei o kit de script de um dos BluePrints (Para fazer isso basta clicar no BluePrint e olhar na aba Preferencias, la em cima, em um menu textual vão ter duas opções: Export e Import), renomeei para sh_Hostile, importei no BluePrint da criatura que criei, mudei os eventos OnDeath e OnRespawn para continuar usando os do Dave e mantive o resto. Agora meu Cao Selvagem ataca quando vc chega perto dele, mas ainda não anda (T_T), de acordo com o Readme dos scripts, ele cria uma cópia do monstro que está no BluePrint, logo, a tag é a mesma do BluePrint.

Essa tag é fundamental para criatura conseguir andar pelos locais programados pelo builder, enquanto quando eu criava uma criatura com a tag vt_lobo1 e criava os WayPoints wp_vt_lobo1_0x ele funcionava da primeira vez, depois da primeira morte, o script criava outro lobo, mas com a tag do bluePrint (Que é apenas lobo1). Aí bagunçava tudo.

Mudei o script dele para criar uma criatura com a mesma tag da anterior. Usei a função GetTag(OBJECT_SELF) mas ele parece ter criado um logo com a mesma tag do meu mapa (?!?!). Enfim, isso ainda está pendente para quando tiver mais tempo.

Outra mudança nos scripts foi a criação do meu próprio script para drops estilo WOW ou Ragnarock. Você cria seu um item, informa a chance de drop, joga o item no inventário do blueprint da criatura e pronto. Quando a criatura for criada, meus script calcula quais daqueles itens estão disponíves ou não. Esse funcionou legal, preciso criar uma variavel em cada item chamado sh_iDropRate do tipo float e com valor de 0 a 100 e uma variável global chamada sh_mDropRate que serve como multiplicador da chance de drop. Amanhã eu trago o script e posto aqui.

terça-feira, 10 de junho de 2008

Trabalhando no meu PW

Bom... Finalmente acabaram as provas e os trabalhos da faculdade e terei mais tempo para brincar no ToolSet e tentar programar um pouco do meu jogo.

Como todo fã de MMORPG, eu estou querendo montar um PW (Persistent World). Como não cheguei a programar muita coisa no NWN 1, terei que aprender tudo do zero agora no 2.

Resolvi começar com uma típica fazenda, a Vila dos Tropeiros. Provavelmente ela será o StartPoint dos humanos (Pretendo colocar um StartPoint diferente para cada raça do jogo, como no WOW).

Segue algumas imagens de como ela está ficando:
















sexta-feira, 30 de maio de 2008

Apresentação

Já faz algum tempo que eu trabalho com programação comercial. Desenvolvo programas de ERP e finanças. Mas como todo ex-amante de jogos eletrônicos e principalmente RPG, um dos meus hobbies atual é a criação de jogos. Tentei criar alguma coisa em Delphi a mais ou menos um ano atrás e até lancei 3 partes do meu tutorial usando um componente chamado GLScene para programação OpenGL no Delphi porém, minha dislexia na época para criar meus próprios modelos 3D para personagens e monstros me fizeram dar um tempo.
A pouco tempo eu comprei o NeverWinter Nights 2, um jogo/engine baseado nas regras oficiais do Dungeons & Dragons e que vem com um editor de campanhas incluso. então, nas horas vagas, vou tentar contar estórias de um mundo que criei a muito tempo chamado SubHeaven (Sim. SubHeaven não é uma pessoa, mas sim um lugar. Mas essa história fica pra outra hora) e principalmente seu reino principal, NordHein.

Meu objetivo nesse blog é fazer coisas que eu gosto relacionado ao mundo de RPG: Contar estórias, tentar aprender modelagem 3D novamente, aprender a trabalhar com texturização, aprender e ensinar como programar e montar campanhas no NWN2.

Talvez eu até de um tempo novamente, mas não pretendo abandonar o NWN2 tão cedo (Pelo menos não até sair uma engine melhor e tão simples quanto ele para programação de jogos).

PS: Não! Eu não vou programar jogos em C pois não quero programar jogos como trabalho nem montar minha empresa de games. Tudo o que eu quero é contar estórias e me divertir com isso.
BlogBlogs.Com.Br