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);
}
}
}
BlogBlogs.Com.Br