AzerothCore
Pages :

El sistema ScriptAI

El sistema ScriptAI implementado por AC utiliza una estrategia especial: Patr贸n del observador para implementar una programaci贸n dirigida por eventos que es tambi茅n el CORE de nuestro sistema modular.

Esta gu铆a, junto con nuestro sistema de m贸dulos te permite ampliar el AzerothCore sin parchearlo directamente. 隆Esto le permite actualizar su repositorio manteniendo sus adiciones y personalizaciones libres de conflictos!

Recursos

Lista de hooks

La lista de los hooks se encuentra dentro del archivo ScriptMgr.h

Glosario

  • Hook: Una funci贸n que se declara dentro de un ScriptObject y que es definida por el Listeners
  • ScriptObject: Clase abstracta que debe ser extendida para crear el Observer.
  • Script type: La clase que extiende el ScriptObject y contiene hooks (por ejemplo, PLayerScript, CreatureScript, etc.), cuando extiendes la clase de tipo script est谩s inicializando un Concrete Observer
  • ScriptRegistry: Esta clase contiene el registro de todos los Observers registrados.
  • ScriptMgr: La clase singleton que contiene la lista de todos los hooks disponibles y act煤a como un Observer notificando a los Listeners cuando se despacha un evento.

C贸mo crear un hook

隆No se preocupe, no es tan aterrador como puede pensar!.

hook

Antes de pasar al siguiente paso deber铆as preguntarte: 驴tengo que crear un nuevo tipo de script basado en la clase ScriptObject o puedo reutilizar uno de los ya existentes?

Un tipo de script suele estar estrictamente relacionado con una determinada clase del core. Por ejemplo:

  • PlayerScript -> Player class
  • WorldScript -> World class
  • CreatureScript -> Creature class

Y as铆 sucesivamente.

Hay algunas excepciones como el GlobalScript que es un Observador utilizado en diferentes clases a lo largo del n煤cleo. Pero en general, un tipo de script debe referirse a una clase espec铆fica.

Por lo tanto, si ha creado una nueva clase que tiene que ser extendida con hooks, entonces puede proceder con el primer punto.

Sin embargo, la mayor铆a de las veces s贸lo tienes que a帽adir nuevos hooks a los scripts existentes, en este caso s贸lo salta al punto 2 de este cap铆tulo.

1) Procedimiento est谩ndar al a帽adir nuevas clases de scripts

En primer lugar, define la clase actual, y haz que herede de ScriptObject, as铆:

class MyScriptType : public ScriptObject
{
    uint32 _someId;
    private:
        void RegisterSelf();
    protected:
        MyScriptType(const char* name, uint32 someId)
            : ScriptObject(name), _someId(someId)
        {
            ScriptRegistry<MyScriptType>::AddScript(this);
        }
    public:
        // Si una funci贸n virtual en tu clase de tipo script
        // no tiene que ser necesariamente anulada, simplemente decl谩rala virtual
        // con un cuerpo vac铆o. Si, por el contrario, es l贸gico anularla
        // (es decir, si es el 煤nico m茅todo de la clase), hazla virtual pura, a帽adi茅ndole = 0.
        virtual void OnBeforeSomeEvent(uint32 /*someArg1*/, std::string& /*someArg2/*) { }
        // Esta es una funci贸n virtual pura:
        virtual void OnAnotherEvent(uint32 /*someArg*/) = 0;
}

A continuaci贸n, tienes que a帽adir una especializaci贸n para ScriptRegistry. Pon esto al principio de ScriptMgr.cpp:

template class ScriptRegistry<MyScriptType>;

Ahora a帽ade el registro en la parte inferior del ScriptMgr.cpp:

MyScriptType::MyScriptType(const char* name)
    : ScriptObject(name)
{
    ScriptRegistry<MyScriptType>::AddScript(this);
}

Entonces a帽ade una rutina de limpieza en ScriptMgr::unload().

SCR_CLEAR(MyScriptType);

隆Y finalmente tu clase est谩 lista para funcionar con el sistema de scripts!

2) Implementar las funciones de los hooks

Si no has seguido el punto 1 y quieres reutilizar un ScriptObject existente, entonces tienes que declarar primero las funciones dentro de una de las clases de ScriptObject preexistentes (como PlayerScript, ServerScript, etc.)

Declarar los hooks

Lo que tienes que hacer ahora es a帽adir funciones a ScriptMgr que puedan ser llamadas desde el n煤cleo para desencadenar realmente ciertos eventos.

En ScriptMgr.h, dentro de class ScriptMgr

void OnBeforeSomeEvent(uint32 someArg1, std::string& someArg2);
void OnAnotherEvent(uint32 someArg);

NOTA: para ciertos scripts el m茅todo declarado dentro de la clase ScriptMgr y el declarado en el ScriptObject relacionado, no siempre coinciden. Por ejemplo: OnLogin es un hook del PlayerScript que se declara como OnPlayerLogin cuando se utiliza dentro de la clase ScriptMgr, evitando as铆 colisiones con otros m茅todos ya que la clase ScriptMgr recoge los hooks de todos los ScriptObjects dentro de la misma lista.

Defina sus hooks

Este paso define la forma en que su hook debe llamar a los listeners registrados.

La forma m谩s habitual de hacerlo es la siguiente

En ScriptMgr.cpp:

void ScriptMgr::OnBeforeSomeEvent(uint32 someArg1, std::string& someArg2)
{
    FOREACH_SCRIPT(MyScriptType)->OnBeforeSomeEvent(someArg1, someArg2);
}

void ScriptMgr::OnAnotherEvent(uint32 someArg)
{
    FOREACH_SCRIPT(MyScriptType)->OnAnotherEvent(someArg);
}

Ahora basta con llamar a estas dos funciones desde cualquier lugar del core para activar el evento en todos los scripts registrados de ese tipo.

C贸mo llamar a tus hooks

La clase ScriptMgr se inicializa dentro del AC como un singleton que contendr谩 todos los observers (ScriptObjects) y sus listeners registrados relacionados (hooks). AC proporciona una propiedad global llamada "sScriptMgr" que puede utilizar para llamar a su script dentro de las funciones de AC.

Por ejemplo:

void CoreClass::SomeEvent() 
{
    uint32 arg1=10;
    std::string arg2="something";

    sScriptMgr->OnBeforeSomeEvent(arg1, arg2);

    //[...]
}

Documentar su hook

Recuerda documentar tu nuevo hook siguiendo la gu铆a C贸mo documentar tu c贸digo.

Cuando creas un nuevo hook para publicarlo en el repo de AzerothCore, uno de los criterios de aceptaci贸n es escribir una documentaci贸n adecuada para 茅l, para que otras personas sepan c贸mo usarlo correctamente. As铆 que, por favor, lee esa gu铆a con atenci贸n.

Escribir un registro de cambios

Cuando creas o modificas cualquier hook, tienes que crear un nuevo changelog para explicar a la gente c贸mo adaptar su c贸digo y mantenerlos informados sobre estos cambios. Por favor, siga esta gu铆a para aprender a hacerlo.

Convenciones de nomenclatura

Cada hook debe tener la siguiente convenci贸n de nombres:

On[When]<Action>

Por ejemplo:

  • OnBeforeConfigLoad
  • OnAfterArenaRatingCalculation

La acci贸n normalmente coincide con el nombre de la funci贸n dentro de la cual se llama al hook.

Si la funci贸n madre es lo suficientemente compleja como para contener diferentes hooks, entonces la acci贸n debe reflejar para qu茅 se utiliza el hook.

La parte "Cuando" es opcional, pero se recomienda encarecidamente.

Ayuda a entender en qu茅 parte de la funci贸n padre se llama al hook.

Por ejemplo, puedes tener tanto OnBeforeConfigLoad como OnAfterConfigLoad, para cambiar el comportamiento antes y despu茅s de cargar la configuraci贸n.

Hooks avanzados

C贸mo cambiar el comportamiento de una funci贸n (filtrado)

Con los hooks no s贸lo puedes ejecutar acciones espec铆ficas en un momento determinado, incluso puedes cambiar el comportamiento de la funci贸n donde se llama al hook para hacerlo, tienes 2 soluciones:

1) Utilizaci贸n de los par谩metros de referencia

Este es el m谩s com煤n. B谩sicamente utilizando el concepto de pasar un par谩metro por referencia se puede cambiar todo lo que se pasa al propio hook.

Por ejemplo:

OnMotdChange(std::string& newMotd)

Pasando el newMotd con el car谩cter '&' se permite a los listeners cambiar el valor del Motd cuando se llama a esa acci贸n.

2) Utilizar un valor de retorno bool

Este enfoque no es muy com煤n, la mayor铆a de los hooks devuelven un tipo "void", y trabajar con referencias es m谩s f谩cil la mayor铆a de las veces, pero si realmente lo necesitas puedes implementar un hook declarado de esta manera:

bool ScriptMgr::OnBeforePlayerTeleport(Player* player, uint32 mapid, float x, float y, float z, float orientation, uint32 options, Unit* target)
{
    bool ret = true;

    FOR_SCRIPTS_RET(PlayerScript, itr, end, ret) // Devuelve true por defecto si no son scripts
    if (!itr->second->OnBeforeTeleport(player, mapid, x, y, z, orientation, options, target))
        ret = false; // Cambiamos el valor de ret s贸lo cuando los scripts devuelven false

    return ret;
}

Este hook notifica a todos los listeners pero tambi茅n captura cuando al menos uno de los listeners registrados devuelve "false", en ese caso el valor de retorno final tambi茅n ser谩 false.

En este caso particular, este hook se utiliza dentro de una condici贸n if para no permitir que un jugador sea teletransportado si uno de los listeners devuelve false por alguna raz贸n.

Puedes implementar tu l贸gica diferente (por ejemplo, falso por defecto, verdadero si lo hay) s贸lo recuerda documentarlo adecuadamente.

Cree su sistema de hooks dentro de su m贸dulo

Usando la gu铆a anterior puedes incluso crear tu ScriptObject dentro de tu m贸dulo para permitir que la gente lo extienda.

Algunos m贸dulos, como el de balance autom谩tico, permiten personalizar cierta parte de su funci贸n mediante el uso de hooks internos.

Puede ver este archivo como ejemplo: https://github.com/azerothcore/mod-autobalance/blob/master/src/AutoBalance.h

NOTA: Tambi茅n necesitas crear tu propia implementaci贸n de ScriptMgr y ofrecer un singleton que permita llamar a tus hooks.

Consideraciones finales

Existen otras caracter铆sticas del sistema ScriptAI que no han sido incluidas en esta documentaci贸n, como la creaci贸n de scripts vinculados a entidades espec铆ficas dentro de nuestra base de datos (Ej. CreatureScript). Este uso avanzado puede ser implementado replicando el c贸digo relacionado que tenemos dentro de los archivos ScriptMgr. Si necesitas ayuda o quieres mejorar esta documentaci贸n, no dudes en pedir apoyo y editar esta p谩gina.

Recursos externos