Buenas noches, y sean ustedes bienvenidos a una nueva guía de apertura sobre C++ para trinitycore. Vamos a analizar un script aprobado y que si fueron en algún momento jugadores del bando alianza, seguramente van a conocer esta misión. http://www.wowhead.com/quest=219/missing-in-action

La misión consiste en seguir, sin perder el vista, el recorrido que tiene un npc, que arranca cuando nosotros aceptamos la misma, pudiéndose realizar por más de 1 jugador si los mismos se encuentran en grupo.

Vamos a analizar parte a parte el código, al final del post, dejaremos el fix realizado por la gente de TC y que al compilar nuestro server, no deberíamos de tener problemas en que la quest funcione sin problemas.

# 1: Importación de librerías.

Código:
#include"ScriptMgr.h"
#include "ScriptedCreature.h"
#include "ScriptedEscortAI.h"
#include "Player.h"
# 2: Declaración de constantes.
Normalmente, vamos a utilizar valores, que sería interesante almacenar de forma representativa con palabras, porque suelen ser utilizada en varios lugares, y siempre es mas fácil entender con palabras, que leer solo números. Además, si el id de la quest, del spell, de un dialogo cambiara, solo tenemos que cambiarlo dentro de la declaración de constantes (recomendaciones de programación)

Código:
enum CorporalKeeshan
{
	 QUEST_MISSING_IN_ACTION = 219,
	SAY_CORPORAL_1          = 0,
	SAY_CORPORAL_2          = 1,
	SAY_CORPORAL_3          = 2,
	SAY_CORPORAL_4          = 3,
	SAY_CORPORAL_5          = 4,
	SPELL_MOCKING_BLOW  = 21008,
	SPELL_SHIELD_BASH      = 11972
};
Podrán observar, que existen formas de representar la información, a las misiones se le pone el prefijo QUEST_ a los diálogos el SAY_ y al caso de los spells SPELL_.
Podrán buscar la quest y los spells a través de los ids definidos aquí.

Otra cosa que es normal en programación, es que si tenemos 5 diálogos, como en el ejemplo, veremos que los mismos se enumeran de 0 a 4, en vez de 1 a 5.

Sigamos con el script...

El esqueleto de prácticamente cualquier npc del juego, se compone de...

Código:
class npc_corporal_keeshan : public CreatureScript {
public:
	npc_corporal_keeshan() : CreatureScript("npc_corporal_keeshan") { }
};

void AddSC_redridge_mountains()
{
    new npc_corporal_keeshan();
}
Básicamente, lo que estamos diciendo en esas líneas, que el script que estamos creando de nombre " npc_corporal_keeshan" hereda de la clase CreatureScript, es decir, tiene acceso a todos los atributos y métodos que sean al menos públicos. En c++ recuerden que se tienen de 3 tipos de "accesos": public, private y protected.

Una vez que definimos el nombre y le decimos que estamos heredando de CreatureScript, viene la palabra mágica "public" que no nos debemos de olvidar, porque si no nos funcionaria nada y la instancia del script, con:

Código:
npc_corporal_keeshan() : CreatureScript("npc_corporal_keeshan") { }
Por último, siempre demos agregar, al final del fichero / archivos, todas las llamadas a las instancias de los distintos scripts que escribamos, porque podemos tener varios scripts dentro de un solo archivo .cpp

Código:
void AddSC_redridge_mountains()
{
    new npc_corporal_keeshan();
}
Si entendemos el esqueleto, lo que sigue, puede parecer un poco mas difícil, pero es solamente mirar ejemplos y verán que la mayoría siempre utiliza, generalmente los mismos llamados a métodos o struct.

Vamos por el que creo, es el más sencillo de todo, que es el método:
void QuestAccept --> Cuando la quest es aceptada.

Código:
void QuestAccept(Player* player, Quest const* quest) override
{
	if (quest->GetQuestId() == QUEST_MISSING_IN_ACTION)
	{
		Talk(SAY_CORPORAL_1, player);
        		me->SetFaction(FACTION_ESCORTEE_N_NEUTRAL_ACTIVE);
        		EscortAI::Start(true, false, player->GetGUID(), quest);
    }
}
Vamos por partes, lo primero que se puede observar, es que el método, recibe 2 parámetros, generalmente, si se encuentra fuera de un struct que luego veremos de qué se trata, puede que reciba 1 mas, que es el npc involucrado, pero aquí no va a hacer necesario, porque dentro del struct, el npc es me

player es un puntero a la clase Player y quest a la clase Quest

Como hicimos mención en herencia, con los punteros pasa exactamente lo mismo, podemos hacer uso de los métodos definidos en las clases, player tiene acceso a los métodos de Player y quest tiene a los de Quest.

En este caso, pregunta a través de una condición, si la id de la quest que se acepta, es igual a la id definida en la constante. Si mañana el "id" cambiara, que generalmente no cambia, con cambiar el numero en la constante enum, bastaría para que el script funcione de todos modos.

Código:
if (quest->GetQuestId() == QUEST_MISSING_IN_ACTION)
Si la condición se cumple, que sería el equivalente a decir:

Código:
if (quest->GetQuestId() == 219)
--> pero siempre usen constantes

Talk -> le dice al npc, que id de dialogo debe ser y a que target, es decir al jugador.

Código:
Talk(SAY_CORPORAL_1, player);
Luego, se cambia la facción, porque dentro de la cueva, tiene una para no ser atacado hasta que nosotros comenzamos la quest, porque caso contrario, podría estar muerto antes de que nosotros llegamos al npc y nunca podríamos iniciar la misma.

Código:
me->SetFaction(FACTION_ESCORTEE_N_NEUTRAL_ACTIVE);
Recuerden que me, dentro de la struct, siempre es una referencia en este caso al npc. Nose si en caso de heredar de PlayerScript si exista esa referencia, deberíamos de revisar información sobre ese tema.

Ahora viene la parte interesante, luego del cambio de facción, llama a Struct, más precisamente al método start, que se encarga de que comience el waypoint, es decir, el recorrido que tenga en la base de datos el npc definido.

Código:
EscortAI::Start(true, false, player->GetGUID(), quest);
Puede que EscortAI, dependiendo de la versión del emulador, les aparezca con otro nombre, en mi caso es: npc_escortAI

Código:
npc_escortAI::Start(bool isActiveAttacker /* = true*/, bool run /* = false */, uint64 playerGUID /* = 0 */, Quest const* quest /* = NULL */, bool instantRespawn /* = false */, bool canLoopPath /* = false */, bool resetWaypoints /* = true */)
Esos son todos los valores que acepta la quest, en nuestro ejemplo le pune true, es decir tiene activo el ataque. Luego false, es decir, no puede correr. Luego tiene el playerGUID, lo sacamos de la función player->GetGUID() y finalmente la quest. Quizás anteriormente pedía esos parámetros, deberíamos de revisar en el emulador y adaptarlo para la nueva definición.

El struct básico, suele estar compuesto de:

Código:
struct npc_corporal_keeshanAI : public EscortAI
    	{
		npc_corporal_keeshanAI(Creature* creature) : EscortAI(creature) {}
	};

	CreatureAI* GetAI(Creature* creature) const override
	{
    		return new npc_corporal_keeshanAI(creature);
	}
El nombre, la clase de la que hereda, es decir EscortAI, ScriptAI, BossAI, npc_escortAI. Esos son los más utilizados. Cada uno, dependiendo de lo que se necesite programar claramente. No tiene mucha lógica, ponerle un EscortAI a un boss de una instancia quizás, por ejemplo, tuetano, pero quizás otro sí.

Entonces, continuando con la explicación, esa sería la estructura básica, en nuestro ejemplo, tenemos muchas llamadas a métodos, que vamos a ir mencionando... QuestAccept o también conocido como OnQuestAccept

Luego, tenemos un método llamado Initialize, que no se encuentra de npc_escortAI, sino que es un método para en este caso, darle valores iníciales a unas variables:
- Phase
- Timer
- mockingBlowTimer
- shieldBashTimer

Y lo llama, dentro del constructor del AI y en el método reset.

Código:
npc_corporal_keeshanAI(Creature* creature) : EscortAI(creature)
    {
        Initialize();
    }

    void Initialize()
    {
        timer = 0;
        phase = 0;
        mockingBlowTimer = 5000;
        shieldBashTimer = 8000;
    }

    void Reset() override
    {
        Initialize();
    }
El metodo que sigue, evalua los valores de los waypoints de la tabla y en determinado puntos, realiza deternadas acciones.

Código:
void WaypointReached(uint32 waypointId, uint32 /*pathId*/) override
{
    Player* player = GetPlayerForEscort();
    if (!player)
        return;

    if (waypointId >= 65)
        me->SetWalk(false);

    switch (waypointId)
    {
        case 39:
            SetEscortPaused(true);
            timer = 2000;
            phase = 1;
            break;
        case 65:
            me->SetWalk(false);
            break;
        case 115:
            player->GroupEventHappens(QUEST_MISSING_IN_ACTION, me);
            timer = 2000;
            phase = 4;
            break;
    }
}
Primero, si no tenemos jugador !player entonces, retornamos, es decir, salimos del script, eso pasa cuando el jugador se aleja demasiado del npc o cuando el jugador muere, para que no quede en el medio del campo y vuelva a su lugar de origen para que podamos nuevamente tomar la quest.

Código:
Player* player = GetPlayerForEscort();
    if (!player)
        return;
Y luego, comenzamos a evaluar los puntos. Los valores de comparación, los podríamos obtener de la tabla, con la siguiente consulta.
Código:
SELECT * FROM script_waypoint WHERE entry IN (349);
En el 39, se frena, pone el timer en 2000, es decir, 2milisegundos, y lo cambia de fase, a 1. Recuerden que inicialmente estaba en cero. Y cuando llega al 115, le da el crédito a todos los del grupo, cambia los valores de la fase.

Ahora la parte más entretenida, el updateAI, básicamente todo se resume a esto, aca vamos a evaluar constantemente los pulsos de la CPU y le vamos a ir pidiendo que realice acciones, dependiendo siempre generalmente de un contador, o timer.

Código:
void UpdateAI(uint32 diff) override
{
    if (HasEscortState(STATE_ESCORT_NONE))
        return;

    EscortAI::UpdateAI(diff);

    if (phase)
    {
        if (timer <= diff)
        {
            switch (phase)
            {
                case 1:
                    me->SetStandState(UNIT_STAND_STATE_SIT);
                    timer = 1000;
                    phase = 2;
                    break;
                case 2:
                    Talk(SAY_CORPORAL_2);
                    timer = 15000;
                    phase = 3;
                    break;
                case 3:
                    Talk(SAY_CORPORAL_3);
                    me->SetStandState(UNIT_STAND_STATE_STAND);
                    SetEscortPaused(false);
                    timer = 0;
                    phase = 0;
                    break;
                case 4:
                    Talk(SAY_CORPORAL_4);
                    timer = 2500;
                    phase = 5;
                    break;
                case 5:
                    Talk(SAY_CORPORAL_5);
                    timer = 0;
                    phase = 0;
                    break;
            }
        } else timer -= diff;
    }

    if (!UpdateVictim())
        return;

    if (mockingBlowTimer <= diff)
    {
        DoCastVictim(SPELL_MOCKING_BLOW);
        mockingBlowTimer = 5000;
    } else mockingBlowTimer -= diff;

    if (shieldBashTimer <= diff)
    {
        DoCastVictim(SPELL_MOCKING_BLOW);
        shieldBashTimer = 8000;
    } else shieldBashTimer -= diff;

    DoMeleeAttackIfReady();
}
En resumen, si no tiene escort, retorna, es decir, sale de la ejecución. Si existe fase, chequea el diff del parametro, con el timer. En caso de estar en la fase 1, Se sienta, timer en 1000 y cambia de fase. Si esta en la 2, dice el 3 dialogo, timer en 15s y fase 3. Fase 3, se para, hace una pausa e inicia las variables en cero. Fase 4, Dice el dialogo 5, timer y cambio de fase. Fase 5, dialogo 6, reinicio de contadores.

Si no tiene victima

Código:
if (!UpdateVictim())
        return;
Retorna a la ruta, por eso muchas veces, van a ver que un npc, si tiene un enemigo, lo ataca, y vuelve varios pasos para atrás, hasta el lugar de donde partió inicialmente, antes de entrar en combate.

Y finalizando, utiliza los spells, dependiendo del timer, si no tiene spells disponibles por tema de CD, realiza ataques melee hasta que pueda utilizar alguno de los 2.

Definimos las variables, como privadas

Código:
private:
    uint32 phase;
    uint32 timer;
    uint32 mockingBlowTimer;
    uint32 shieldBashTimer;
Si no recuerdo mal, siempre son accesibles solamente desde esta clase, ese es el concepto de privado, aunque a veces con los lenguajes cambian y no soy un experto en c++

Y listo. Eso sería toda la explicación. Podes ver el script para la 3.3.5 en:

Script TC

Los waypoints de este npc, podríamos obtenerlos con la siguiente consulta

349 es el entry del npc que estamos fixeando. Podremos encontrar todo el recorrido que realiza en esa tabla, y observar todo el script, visitando los puntos.

No hay que olvidarse, antes de compilar el script, Incorporarlo en el scriptLoader.cpp al final del fichero, eso también cambia dependiendo de la versión del emulador.

Código:
#ifdef SCRIPTS
/* This is where custom scripts' loading functions should be declared. */
    void AddSC_npc_anduin_wrynn();
#endif

void AddCustomScripts()
{
#ifdef SCRIPTS
    /* This is where custom scripts should be added. */
    AddSC_npc_anduin_wrynn();
#endif
}
Y decirle en la DB, que el npc tiene un script de C++

Código:
UPDATE creature_template SET ScriptName="npc_corporal_keeshan", AIname="" WHERE entry IN (349);
Cursos de SQL con Fearz.
No olvides dejar tus FearzPoint, si te gusto el post.