Tutorial de Script C++ World of Warcraft 2:

Iniciado por Nuzak, Abr 20, 2025, 02:29 PM

Tema anterior - Siguiente tema

0 Miembros y 1 Visitante están viendo este tema.

Todos los créditos a mariodanny91 por su publicación original en WoWCreador
En esta guía, enseñaré una de las maneras de hacer un script C++ para el emulador JadeCore que está basado en TrinityCore antiguo. Les servirá de conocimiento base para otros emuladores más actualizados y motivarlos a investigar mas sobre el tema. Vale aclarar que todo lo planteado aquí es de manera autodidacta no soy programador, solo comparto lo que he aprendido.

Para esta guia deben tener instalado Visual Studio, Cmake y otras dependencias que exija el core ya sea OpenSSL,Boost,Mysql en sus versiones x86 o x64.


Tutorial anterior:

You are not allowed to view links. Register or Login

Tutorial de Script C++ World of Warcraft 1:  En esta guía, enseñaré una de las maneras de hacer un script C++ para el emulador JadeCore  que está basado en TrinityCore antiguo.  Les servirá de conocimiento base para otros emuladores más actualizados y motivarlos a investigar mas sobre el tema...


Punto sobre los cuales hablaré:

-Fases.
-Como hacer un Intro.

-Controlar invocaciones.


-Primero vamos a repasar algunos conceptos basicos de C++ y detalles referente al core:


Operadores Logicos:

        C++:
&&  And ó Y 
||  Or ó O
!    Not ó No

Operadores Relacionales:

        C++:
==    Igual que
<    menor que
>    mayor que
<=    menor o igual que
>=    mayor o igual que
!=    no es igual o desigual
=    asignacion(no confundir con igual que)

Operadores Matematicos:

        C++:
-    restar
+    sumar
*    multiplicar
/    dividir
%    modulus(devuelve el resto de una division)

-Tipos de variables mas utilizadas:

        C++:

Tipo        Valores
bool        true o false
float        los llamados numeros con coma.(1.2f)
int            numeros enteros incluye positivos y negativos.
uint32        numeros enteros solo positivos.
uint64        numeros enteros solo positivos.
uint significa unsigned int

*La diferencia entre uint8, uint16, uint32, uint64 son los valores que pueden almacenar en memoria.



        C++:

Tipo    Valores
uint8     0 a 32 768
uint16    0 a 65 535
uint32    0 a 4 294 836 225
uint64    0 a 8 589 672 450

-Como declarar una variable:
Los nombres de las variables no pueden tener espacios y no puede coincidir con las palabras claves utilizadas por el compilador:

Ej: auto, else, new, true, case, class, if, return, void, default, break...

        C++:
tipo espacio variable punto y coma
uint32 timer intro; (MAL)
uint32 break; (MAL)
uint32 timer_intro; (BIEN)
uint32 timerintro; (BIEN)

-Despues de declarar le damos un valor a las variables:

        C++:

timerintro = 0;
-Algunos detalles del core que se deben conocer:


-Constantes de Tiempo:
(Los tiempos se declaran en milisegundos)


        C++:

MINUTE          = IN_MILLISECONDS * 60,
HOUR            = MINUTE*60,
DAY             = HOUR*24,
WEEK            = DAY*7,
MONTH           = DAY*30,
YEAR            = MONTH*12,
IN_MILLISECONDS = 1000

1000 milisegundos equivale a 1 segundo.
Seria igual decir 1 * IN_MILLISECONS, es 1 multiplicado por la constante de tiempo IN_MILLISECONDS = 1000.

-Que es urand:


        C++:

urand(uint32 min, uint32 max);
urand(tiempo minimo, tiempo maximo);
urand(15000, 20000)
urand(15, 20) * IN_MILLISECONDS
*El evento va a ocurrir de manera randon en un tiempo entre 15 segundos y 20 segundos.

-Podemos hacer el casteo de una magia tanto con el ID de la magia, como con una asignacion del enum Spell:

* Los enum no son mas que declaraciones constantes con las cuales trabajaremos un poco mas organizado ya que es mas facil
reconocer en el codigo el nombre de la magia que el ID.


        C++:

enum Spell
{
    SPELL_PRESENCIA_DE_ESCARCHA        = 48266,
}
me->CastSpell(me, SPELL_PRESENCIA_DE_ESCARCHA, 0);
me->CastSpell(me, 48266, 0);

*Tambien se aplica a invocaciones, textos, fases,..


-Estructura de los eventos:

        C++:

events.ScheduleEvent(uint32 eventId, uint32 time, uint32 groupId = 0, uint32 phase = 0);
events.ScheduleEvent(EVENT_PAIN_AND_SUFFERING, 5000, 0, PHASE_LICH_KING);
events.SetPhase(uint32 phase);
events.SetPhase(PHASE_LICH_KING);
events.IsInPhase(uint8 phase);
events.IsInPhase(PHASE_DK);

Despues de repasar estos detalles referente al core y a C++.

Comenzaremos enumerando los datos que vamos a utilizar:


        C++:

enum Npc
{
    NPC_SUMMON_DK = 31325,
    NPC_SUMMON_LK = 31754,       
};
enum Texts
{
    SAY_INTRO                = 0,
    SAY_PHASE_DK            = 1,       
    SAY_SUMMON_DK            = 2,
    SAY_SUMMON_LK            = 3,       
    SAY_PHASE_LICH_KING        = 4,
    SAY_SLAY                = 5,
    SAY_DEATH                = 6,
};
enum Spells
{           
    SPELL_PRESENCIA_DE_ESCARCHA            = 48266,
    //DK   
    SPELL_EXPLOSION_AULLANTE            = 61061,
    SPELL_GOLPE_DE_ESCARCHA                = 79895,
    SPELL_INVIERNO_SIN_REMORDIMIENTO    = 108200,                   
    //LICH_KING
    SPELL_SOUL_REAPER                    = 69409,
    SPELL_PAIN_AND_SUFFERING            = 72133,
};
enum Events
{
    EVENT_INTRO                            = 1,
    EVENT_SUMMON_DK                        = 2,
    EVENT_SUMMON_LK                        = 3,
    //DK
    EVENT_EXPLOSION_AULLANTE            = 4,
    EVENT_GOLPE_DE_ESCARCHA                = 5,
         
    //LICH_KING
    EVENT_INVIERNO_SIN_REMORDIMIENTO    = 6,
    EVENT_SOUL_REAPER                    = 7,
    EVENT_PAIN_AND_SUFFERING            = 8,
};
         
enum Phases
{
    PHASE_INTRO            = 1,
    PHASE_DK            = 2,       
    PHASE_LICH_KING        = 3,       
};

-Agregar los eventos con sus respectivos tiempos:
Al declararse en la funcion void EnterCombat empezaran a contar el tiempo en el momento que empiece el combate y tambien empezara la fase PHASE_DK.


        C++:

void EnterCombat(Unit* who)
{
    events.SetPhase(PHASE_DK);
    events.ScheduleEvent(EVENT_SUMMON_DK, 30000, 0, PHASE_DK);
    events.ScheduleEvent(EVENT_EXPLOSION_AULLANTE, urand(10, 15) * IN_MILLISECONDS, 0, PHASE_DK);
    events.ScheduleEvent(EVENT_GOLPE_DE_ESCARCHA, 8000, 0, PHASE_DK);
}

-Crear los eventos con sus respectivos spell, summon, textos, etc:

        C++:

while (uint32 eventId = events.ExecuteEvent())
                    {
                        switch (eventId)
                        {
                        case EVENT_SUMMON_DK:
                            Talk(SAY_SUMMON_DK);
                            me->SummonCreature(NPC_SUMMON_DK, 0.0f, 0.0f, 0.0f, 0.0f, TEMPSUMMON_CORPSE_DESPAWN);
                            events.ScheduleEvent(EVENT_SUMMON_DK, 30000, 0, PHASE_DK);
                            break;                   
                        case EVENT_EXPLOSION_AULLANTE:
                            if (Unit * target = SelectTarget(SELECT_TARGET_RANDOM, 0, 30.0f, false))
                                DoCast(target, SPELL_EXPLOSION_AULLANTE);
                            events.ScheduleEvent(EVENT_EXPLOSION_AULLANTE, 10000, 0, PHASE_DK);
                            break;
                        case EVENT_GOLPE_DE_ESCARCHA:
                            DoCastVictim(SPELL_GOLPE_DE_ESCARCHA);
                            events.ScheduleEvent(EVENT_GOLPE_DE_ESCARCHA, 8000, 0, PHASE_DK);
                            break;
                         
                        case EVENT_SUMMON_LK:
                            Talk(SAY_SUMMON_LK);
                            me->SummonCreature(NPC_SUMMON_LK, 0.0f, 0.0f, 0.0f, 0.0f, TEMPSUMMON_CORPSE_DESPAWN);
                            events.ScheduleEvent(EVENT_SUMMON_LK, 30000, 0, PHASE_LICH_KING);
                            break;
                        case EVENT_SOUL_REAPER:
                            DoCastVictim(SPELL_SOUL_REAPER);
                            events.ScheduleEvent(EVENT_SOUL_REAPER, urand(15000, 20000), 0, PHASE_LICH_KING);
                            break;
                        case EVENT_PAIN_AND_SUFFERING:
                            DoCastToAllHostilePlayers(SPELL_PAIN_AND_SUFFERING);
                            events.ScheduleEvent(EVENT_PAIN_AND_SUFFERING, 5000, 0, PHASE_LICH_KING);
                            break;
                        default:
                            break;
                        }
                    }

-Crear condicion para cambio de fase:

        C++:

if (events.IsInPhase(PHASE_DK) && HealthBelowPct(50))
                    {       
                        events.SetPhase(PHASE_LICH_KING);
                        Talk(SAY_PHASE_LICH_KING);
                        me->CastSpell(me, SPELL_INVIERNO_SIN_REMORDIMIENTO, 0);
                        me->SetDisplayId(30721);
                        events.ScheduleEvent(EVENT_SUMMON_LK, 30*IN_MILLISECONDS, 0, PHASE_LICH_KING);
                        events.ScheduleEvent(EVENT_SOUL_REAPER, urand(15, 20)*IN_MILLISECONDS, 0, PHASE_LICH_KING);
                        events.ScheduleEvent(EVENT_PAIN_AND_SUFFERING, 5000, 0, PHASE_LICH_KING);                   
                    }

*Si los evento estan en PHASE_DK y los puntos de vida estan por dejajo del 50% cambiar a fase PHASE_LICH_KING y empezar a ejecutar los eventos de esa fase.


Nota:Las condiciones son un tema delicado en C++ ya que deben estar bien especificadas para no generar errores:
Ej:

 
 
        C++:
 
if (player->GetQuestStatus(12345) == QUEST_STATUS_COMPLETE)
{
    Talk(0);
}

Si el jugador tiene la mision 12345 completada, el npc hablara.
Es decir que mientras el jugador tenga la mision completada el npc hablara y hablara y hablara indefinidamente y obtendriamos esto:

Los visitantes no pueden visualizar imágenes en los mensajes, por favor You are not allowed to view links. Register or Login o You are not allowed to view links. Register or Login



Pero si agregamos una variable de tipo booleana (bool):

 
 
        C++:
 
if (player->GetQuestStatus(12345) == QUEST_STATUS_COMPLETE) && (!talk)
{
    Talk(0);
    talk = true;
}

(!talk es lo mismo que decir talk=false)
En este caso la condicion dice si el jugador tiene la mision 12345 completada y la variable talk es false, el npc hablara y talk sera true.

Al ser talk true se deja de cumplir la condicion y solo hablara una sola vez.


2-Como hacer un intro:


Con estas lineas crearemos un intro de manera practica:
 
 
        C++:
 
std::list playerList;
            GetPlayerListInGrid(playerList, me, 30.0f);
            for (auto player : playerList)
            {
             
            }

*El npc creara una lista de los player que esten a su alrededor en un rango de 30 metros, algo asi como un radar.
Se ejecutara una accion si un player entra en ese rango.

-Quedaria asi:

 
 
        C++:
 
std::list playerList;
            GetPlayerListInGrid(playerList, me, 30.0f);
            for (auto player : playerList)
            {
                events.Update(diff);
                while (uint32 eventId = events.ExecuteEvent())
                {
                    switch (eventId)
                    {
                    case EVENT_INTRO:
                    {
                        timerintro++;
                        if (timerintro == 1)
                        {
                            Talk(SAY_INTRO);
                        }
                        if (timerintro == 11)
                        {
                            me->CastSpell(me, 127511, 0);
                        }
                        if (timerintro == 12)
                        {
                            me->SetDisplayId(22235);
                            me->SetObjectScale(2.0f);
                        }
                        if (timerintro == 14)
                        {
                            Talk(SAY_PHASE_DK);
                            me->setFaction(14);
                            me->CastSpell(me, SPELL_PRESENCIA_DE_ESCARCHA, 0);
                            SetEquipmentSlots(false, 33475);
                        }
                        if (timerintro == 18)
                        {
                            AttackStart(player);
                        }
                        events.ScheduleEvent(EVENT_INTRO, 1000);
                        break;
                    }
                    }
                }
            }

*Si un player entra en el rango de 30 metros empezara el EVENT_INTRO y la variable timerintro incrementara su valor a medida que pase el tiempo con timerintro++.
Con las siguientes condiciones a medida que el valor de timerintro incremente se ejecutara el intro.

si timerintro es igual que 1

Hablara


si timerintro es igual que 11
casteara una magia

si timerintro es igual que 12

cambiara su displayID, el modelo visual del npc
cambiara su escala a 2

si timerintro es igual que 14

Hablara
cambiara su faccion a 14(Hostil)

casteara una magia
equipara un arma

si timerintro es igual que 18

comenzara a atacar al player


3-Controlar invocaciones:
Hasta el momento las invocaciones atacan por que son hostiles pero las controla el core.

Controlaremos invocaciones con la funcion void JustSummoned.


 
 
        C++:
 
void JustSummoned(Creature* summon)
        {
            if (summon->GetEntry() == NPC_SUMMON_DK)
            {
                summon->CastSpell(summon, 48265, false);
                if (Unit * target = SelectTarget(SELECT_TARGET_RANDOM, 0))
                    summon->AI()->AttackStart(target);
            }
            if (summon->GetEntry() == NPC_SUMMON_LK)
            {
                summon->CastSpell(summon, 49222, false);
                if (Unit * target = SelectTarget(SELECT_TARGET_BOTTOMAGGRO, 0))
                    summon->AI()->AttackStart(target);
            }
        }

*Si la criatura invocada(summon) es NPC_SUMMON_DK o NPC_SUMMON_LK, castera una magia y seleccionara un objetico para atacar.
Cada invocacion casteara una magia, seleccionara un objetivo diferente y atacara en cuanto sea invocada.

La funcion solamente ejecutara cualquier accion en el momento de la invocacion no despues de invocado el npc.
Podemos hacer que castee una magia, que hable, que camine hacia un lugar, etc..

 
 
        C++:
 
summon->GetMotionMaster()->MovePoint(0, 966.20f, 3602.97f, 196.58f);//se mueva hacia un punto.
summon->CastSpell(summon, 49222, false);//castee una magia.
summon->DespawnOrUnsummon(10*IN_MILLISECONDS);//desaparesca en un tiempo x.
summon->ApplySpellImmune(0, IMMUNITY_SCHOOL, SPELL_SCHOOL_MASK_FROST, true); //aplicar una inmunidad a la magia de tipo frio.
summon->setFaction(14);//cambiarle la faccion.
summon->SetLevel(80);// cambiarle el nivel.
summon->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_DISABLE_MOVE);//agregarle una flag.
Talk(TEXTO);//Hablar.

-Finalmente con la funcion void JustReachedHome() reseteamos el npc:
*Cuando el npc llegue a su posicion inicial antes del combate.

 
 
        C++:
 
void JustReachedHome()
        {
            ScriptedAI::Reset();
        }

Llamara a la funcion void Reset():
 
 
        C++:
 
void Reset()
        {               
            me->setFaction(35);
            me->SetDisplayId(24949);
            me->SetObjectScale(1.0f);
            timerintro = 0;
            SetEquipmentSlots(false, 0);
        }

*Con ella devolveremos los valores iniciales del npc:
faccion, modelo visual, escala, arma equipada y vuelve a 0 la variable timerintro, asi el npc esta disponible nuevamente.

El Script deberia ir quedando asi:

 
 
        C++:
 
/*
* Copyright (C) 2008-2012 TrinityCore
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see .
*/
#include "ScriptMgr.h"
#include "ScriptPCH.h"
//NPC_TUTORIAL
class npc_tutorial : public CreatureScript
{
public:
    npc_tutorial() : CreatureScript("npc_tutorial") { }
    struct npc_tutorialAI : public ScriptedAI
    {
        npc_tutorialAI(Creature* creature) : ScriptedAI(creature)
        {   
            events.ScheduleEvent(EVENT_INTRO, 600);       
        }
        uint32 timerintro;
             
        enum Npc
        {
            NPC_SUMMON_DK = 31325,
            NPC_SUMMON_LK = 31754,       
        };
        enum Texts
        {
            SAY_INTRO                = 0,
            SAY_PHASE_DK            = 1,       
            SAY_SUMMON_DK            = 2,
            SAY_SUMMON_LK            = 3,       
            SAY_PHASE_LICH_KING        = 4,
            SAY_SLAY                = 5,
            SAY_DEATH                = 6,
        };
        enum Spells
        {           
            SPELL_PRESENCIA_DE_ESCARCHA        = 48266,
            //DK   
            SPELL_EXPLOSION_AULLANTE            = 61061,
            SPELL_GOLPE_DE_ESCARCHA                = 79895,
            SPELL_INVIERNO_SIN_REMORDIMIENTO    = 108200,                   
            //LICH_KING
            SPELL_SOUL_REAPER                = 69409,
            SPELL_PAIN_AND_SUFFERING        = 72133,
        };
        enum Events
        {
            EVENT_INTRO                            = 1,
            EVENT_SUMMON_DK                        = 2,
            EVENT_SUMMON_LK                        = 3,
            //DK
            EVENT_EXPLOSION_AULLANTE            = 4,
            EVENT_GOLPE_DE_ESCARCHA                = 5,
         
            //LICH_KING
            EVENT_INVIERNO_SIN_REMORDIMIENTO    = 6,
            EVENT_SOUL_REAPER                    = 7,
            EVENT_PAIN_AND_SUFFERING            = 8,
        };
         
        enum Phases
        {
            PHASE_INTRO            = 1,
            PHASE_DK            = 2,       
            PHASE_LICH_KING        = 3,       
        };
        void Reset()
        {               
            me->setFaction(35);
            me->SetDisplayId(24949);
            me->SetObjectScale(1.0f);
            timerintro = 0;
            SetEquipmentSlots(false, 0);
        }       
        void JustReachedHome()
        {
            ScriptedAI::Reset();
        }
     
        void EnterCombat(Unit* who)
        {
            events.SetPhase(PHASE_DK);
            events.ScheduleEvent(EVENT_SUMMON_DK, 30000, 0, PHASE_DK);
            events.ScheduleEvent(EVENT_EXPLOSION_AULLANTE, urand(10, 15) * IN_MILLISECONDS, 0, PHASE_DK);
            events.ScheduleEvent(EVENT_GOLPE_DE_ESCARCHA, 8000, 0, PHASE_DK);
        }
        void KilledUnit(Unit* victim)
        {
            Talk(SAY_SLAY);
        }
        void JustDied(Unit* victim)
        {
            Talk(SAY_DEATH);
        }       
     
        void JustSummoned(Creature* summon)
        {
            if (summon->GetEntry() == NPC_SUMMON_DK)
            {
                summon->CastSpell(summon, 48265, false);
                if (Unit * target = SelectTarget(SELECT_TARGET_RANDOM, 0))
                    summon->AI()->AttackStart(target);
            }
            if (summon->GetEntry() == NPC_SUMMON_LK)
            {
                summon->CastSpell(summon, 48265, false);
                if (Unit * target = SelectTarget(SELECT_TARGET_BOTTOMAGGRO, 0))
                    summon->AI()->AttackStart(target);
            }
        }       
        void UpdateAI(uint32 const diff)
        {
            std::list playerList;
            GetPlayerListInGrid(playerList, me, 30.0f);
            for (auto player : playerList)
            {
                events.Update(diff);
                while (uint32 eventId = events.ExecuteEvent())
                {
                    switch (eventId)
                    {
                    case EVENT_INTRO:
                    {
                        timerintro++;
                        if (timerintro == 1)
                        {
                            Talk(SAY_INTRO);
                        }
                        if (timerintro == 11)
                        {
                            me->CastSpell(me, 127511, 0);
                        }
                        if (timerintro == 12)
                        {
                            me->SetDisplayId(22235);
                            me->SetObjectScale(2.0f);
                        }
                        if (timerintro == 14)
                        {
                            Talk(SAY_PHASE_DK);
                            me->setFaction(14);
                            me->CastSpell(me, SPELL_PRESENCIA_DE_ESCARCHA, 0);
                            SetEquipmentSlots(false, 33475);
                        }
                        if (timerintro == 18)
                        {
                            AttackStart(player);
                        }
                        events.ScheduleEvent(EVENT_INTRO, 1000);
                        break;
                    }
                    }
                }
            }
                if (!UpdateVictim())
                    return;
                events.Update(diff);
                if (me->HasUnitState(UNIT_STATE_CASTING))
                    return;
                if (events.IsInPhase(PHASE_DK) && HealthBelowPct(50))
                {
                    events.SetPhase(PHASE_LICH_KING);
                    Talk(SAY_PHASE_LICH_KING);
                    me->CastSpell(me, SPELL_INVIERNO_SIN_REMORDIMIENTO, 0);
                    me->SetDisplayId(30721);
                    events.ScheduleEvent(EVENT_SUMMON_LK, 30 * IN_MILLISECONDS, 0, PHASE_LICH_KING);
                    events.ScheduleEvent(EVENT_SOUL_REAPER, urand(15, 20) * IN_MILLISECONDS, 0, PHASE_LICH_KING);
                    events.ScheduleEvent(EVENT_PAIN_AND_SUFFERING, 5000, 0, PHASE_LICH_KING);
                }
                while (uint32 eventId = events.ExecuteEvent())
                {
                    switch (eventId)
                    {
                    case EVENT_SUMMON_DK:
                        Talk(SAY_SUMMON_DK);
                        me->SummonCreature(NPC_SUMMON_DK, 0.0f, 0.0f, 0.0f, 0.0f, TEMPSUMMON_CORPSE_DESPAWN);
                        events.ScheduleEvent(EVENT_SUMMON_DK, 30000, 0, PHASE_DK);
                        break;
                    case EVENT_EXPLOSION_AULLANTE:
                        if (Unit * target = SelectTarget(SELECT_TARGET_RANDOM, 0, 30.0f, false))
                            DoCast(target, SPELL_EXPLOSION_AULLANTE);
                        events.ScheduleEvent(EVENT_EXPLOSION_AULLANTE, 10000, 0, PHASE_DK);
                        break;
                    case EVENT_GOLPE_DE_ESCARCHA:
                        DoCastVictim(SPELL_GOLPE_DE_ESCARCHA);
                        events.ScheduleEvent(EVENT_GOLPE_DE_ESCARCHA, 8000, 0, PHASE_DK);
                        break;
                    case EVENT_SUMMON_LK:
                        Talk(SAY_SUMMON_LK);
                        me->SummonCreature(NPC_SUMMON_LK, 0.0f, 0.0f, 0.0f, 0.0f, TEMPSUMMON_CORPSE_DESPAWN);
                        events.ScheduleEvent(EVENT_SUMMON_LK, 30000, 0, PHASE_LICH_KING);
                        break;
                    case EVENT_SOUL_REAPER:
                        DoCastVictim(SPELL_SOUL_REAPER);
                        events.ScheduleEvent(EVENT_SOUL_REAPER, urand(15000, 20000), 0, PHASE_LICH_KING);
                        break;
                    case EVENT_PAIN_AND_SUFFERING:
                        DoCastToAllHostilePlayers(SPELL_PAIN_AND_SUFFERING);
                        events.ScheduleEvent(EVENT_PAIN_AND_SUFFERING, 5000, 0, PHASE_LICH_KING);
                        break;
                    default:
                        break;
                    }
                }   
                         
            DoMeleeAttackIfReady();
        }
    };
    CreatureAI* GetAI(Creature* creature) const
    {
        return new npc_tutorialAI(creature);
    }
};
void AddSC_tutorial()
{
    new npc_tutorial();
}

Ahora vamos a la parte de SQL:
Crear el NPC con el nombre del script en la columna `ScriptName`:

 
 
        SQL:
 
DELETE FROM `creature_template` WHERE entry=1000000;
INSERT INTO `creature_template`(`entry`, `difficulty_entry_1`, `difficulty_entry_2`, `difficulty_entry_3`, `difficulty_entry_4`, `difficulty_entry_5`, `difficulty_entry_6`, `difficulty_entry_7`, `difficulty_entry_8`, `difficulty_entry_9`, `difficulty_entry_10`, `difficulty_entry_11`, `difficulty_entry_12`, `difficulty_entry_13`, `difficulty_entry_14`, `difficulty_entry_15`, `KillCredit1`, `KillCredit2`, `modelid1`, `modelid2`, `modelid3`, `modelid4`, `name`, `subname`, `IconName`, `gossip_menu_id`, `minlevel`, `maxlevel`, `exp`, `exp_unk`, `faction_A`, `faction_H`, `npcflag`, `npcflag2`, `speed_walk`, `speed_run`, `speed_fly`, `scale`, `rank`, `mindmg`, `maxdmg`, `dmgschool`, `attackpower`, `dmg_multiplier`, `baseattacktime`, `rangeattacktime`, `unit_class`, `unit_flags`, `unit_flags2`, `dynamicflags`, `family`, `trainer_type`, `trainer_spell`, `trainer_class`, `trainer_race`, `minrangedmg`, `maxrangedmg`, `rangedattackpower`, `type`, `type_flags`, `type_flags2`, `lootid`, `pickpocketloot`, `skinloot`, `resistance1`, `resistance2`, `resistance3`, `resistance4`, `resistance5`, `resistance6`, `spell1`, `spell2`, `spell3`, `spell4`, `spell5`, `spell6`, `spell7`, `spell8`, `PetSpellDataId`, `VehicleId`, `mingold`, `maxgold`, `AIName`, `MovementType`, `InhabitType`, `HoverHeight`, `Health_mod`, `Mana_mod`, `Mana_mod_extra`, `Armor_mod`, `RacialLeader`, `questItem1`, `questItem2`, `questItem3`, `questItem4`, `questItem5`, `questItem6`, `movementId`, `RegenHealth`, `equipment_id`, `mechanic_immune_mask`, `flags_extra`, `ScriptName`, `WDBVerified`) VALUES
(1000000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 30721, 0, 0, 0, 'Tutorial', NULL, NULL, 0, 90, 90, 4, 0, 14, 14, 0, 0, 1, 1.14286, 1.14286, 1, 1, 1000, 2000, 0, 1000, 20, 2000, 2000, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1000, 2000, 1000, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, '', 0, 3, 1, 100, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 'npc_tutorial', 1);

Textos del NPC:
 
 
        SQL:
 
DELETE FROM `creature_text` WHERE entry=1000000;
INSERT INTO `creature_text`(`entry`, `groupid`, `id`, `text`, `type`, `language`, `probability`, `emote`, `duration`, `sound`, `comment`) VALUES
(1000000, 0, 0, 'SAY_INTRO', 14, 0, 100, 1, 0, 12732, ''),
(1000000, 1, 0, 'SAY_PHASE_DK', 14, 0, 100, 15, 0, 14685, ''),
(1000000, 2, 0, 'SAY_SUMMON_DK', 14, 0, 100, 25, 0, 14693, ''),
(1000000, 3, 0, 'SAY_SUMMON_LK', 14, 0, 100, 25, 0, 17222, ''),
(1000000, 4, 0, 'SAY_PHASE_LICH_KING', 14, 0, 100, 0, 0, 17352, ''),
(1000000, 5, 0, 'SAY_SLAY', 14, 0, 100, 0, 0, 17214, ''),
(1000000, 6, 0, 'SAY_DEATH', 14, 0, 100, 0, 0, 17374, '');

Compilamos y que lo disfruten.
  •