inmensia |
MMORPG
Juan Mellado, 9 Octubre, 2010 - 10:50
Llegado este punto no me queda mucho por decir. He cubierto todos los apartados que me había propuesto, completando los 10 artículos que calculé que podía llevarme la tarea. Aunque en este último post es bueno echar la vista atrás y repasar lo aprendido, que no ha sido poco. Lua es sin lugar a dudas una de las piezas claves de todo este invento de los addons. Ya lo conocía de antes, pero como otra más de esas tecnologías que uno va encontrando por el camino y a la que nunca acaba de dedicar todo el tiempo que debería. En particular me llamó mucho la atención que Allods levantara una máquina virtual de Lua independiente para cada addon. Gracias a que Lua es un proyecto de código abierto me animé a bajarme los fuentes y echarles un vistazo para comprobar lo liviano que resulta todo el sistema, gracias a un lenguaje que en el fondo es bastante simple pero que cumple con creces las necesidades que se le plantean. Otro factor clave desde el punto de vista del código de los addons es la programación orientada a eventos. A día de hoy sigue pareciendo una de las formas más naturales de informar de la ocurrencia de determinados sucesos externos a un programa, evitando tener que consumir recursos haciendo polling sobre algún tipo de estructura global. La contrapartida por supuesto es el número de eventos, que puede llegar a ser realmente grande si se pretende abarcar el enorme número de sucesos que pueden llegar ocurrir en el juego. Un API bien definido que permita acceder y manipular todos los componentes principales del juego es otro punto importante a tener en cuenta. Aunque al igual que ocurre con los eventos, el número de funciones públicas se dispara. Afortunadamente, gracias al tipo table de Lua, que no deja de ser una tabla hash en los que sus elementos se referencian por su nombre, no es necesario definir múltiples estructuras o clases de datos, ya que se puede añadir cualquier atributo sin tener que modificar el código ya existente. Todo un acierto. El hecho de que el código fuente interno de Allods, al menos en la parte que está escrita en Lua, utilice las mismas funciones del API que se exponen de forma pública es bastante interesante. Eso permite reemplazar funcionalidades completas del juego por las nuestras propias de forma bastante elegante. La interface de usuario en Allods está resuelta mediante ficheros XML donde se describen las ventanas, los controles que contienen, y su aspecto en general. Esto es bastante común en muchas arquitecturas, y aunque tienden a generar muchos ficheros a veces dificiles de mantener manualmente, permite separar claramente la capa de presentación de la lógica de negocio. Aunque en este punto se echa en falta sin lugar a dudas algún tipo de editor visual que facilite la construcción de las interfaces. Y hablando de echar en falta, donde más naufraga Allods en este asunto de los addons es en la falta de documentación. De acuerdo, el traductor de Google es nuestro amigo, pero aún así resulta complicado navegar por la documentación del API que se encuentra única y exclusivamente publicada en ruso. Y por no mencionar la diferencia entre las distintas versiones del juego, la rusa y las demás, con cambios en el API que hacen que los addons no funcionen correctamente en unas versiones y otras. En cualquier caso, siempre hay que tener presente que Allods es un MMORPG gratuíto, aunque con un planteamiento del juego que invita en todo momento al jugador a pasarse por la tienda de items a realizar un micropago para conseguir algún tipo de mejora, o evitar las odiosas maldiciones que hacen que los objetos inviertan el valor de sus estadísticas. Cualquier característica que los desarrolladores incorporen al juego, como la posibilidad de escribir addons, se tendría que valorar en su justa medida teniendo en cuenta su gratuidad. Happy coding!
Juan Mellado, 8 Octubre, 2010 - 16:40
Para ir acabando con esta serie dedicada a los addon de Allods voy a poner un par de bloques de código que he encontrado útiles. Cosas sencillas que suelen ser necesarias hacerlas de vez en cuando. Ocultar la Interface Cuando el usuario solicita que se oculte o restaure la interface del juego (combinación ALT+Z por defecto), Allods lanza el evento "SCRIPT_TOGGLE_UI" con un parámetro de tipo boolean que indica si la interface se está ocultando (false) o restaurando (true). En consecuencia, resulta muy sencillo ocultar o mostrar las ventanas de nuestros addons, consiguiendo un acabado más profesional de los mismos. Como siempre, lo primero es registrar un manejador para el evento: common.RegisterEventHandler(OnToggleUI, "SCRIPT_TOGGLE_UI")Y en la función del manejador ocultar o mostrar nuestra interface: function OnToggleUI(params)Evidentemente, en el código del ejemplo se supone que en el addon hay declarada una variable global "formulario" que contiene una referencia a la ventana principal del addon. Si hubiera varias ventanas independientes habría que realizar la misma operación con todas ellas de forma individual. Guardar Información Lógicamente, por temas de seguridad, Allods no permite que el código de un addon acceda libremente al disco duro de la máquina donde se ejecuta el juego. La máquina virtual de Lua que se utiliza está "capada" en ese sentido. Pero si deja la posibilidad de que se escriba y lea del fichero de configuración de opciones globales del juego. Dicho fichero tiene por nombre "user.cfg", y se encuentra en la carpeta "Personal" del directorio donde se encuentra instalado el juego. Es un fichero de texto ordinario, por lo que se puede abrir y examinar con cualquier editor. Para escribir y leer del fichero hay que utilizar las funciones "SetGlobalConfigSection(section, table)" y "GetGlobalConfigSection(section)" respectivamente. Siendo "section" el nombre de la etiqueta bajo la cual queremos grabar los valores contenidos en "table", que ha de ser una variable de tipo table de Lua. El siguiente código de ejemplo crea una tabla con un valor y la graba: local tabla = {}El resultado de la ejecución del código anterior es la actualización del fichero "user.cfg", al que se le añaden las siguientes líneas dentro de la sección "global": ...El proceso contrario, para leer del fichero, es bien sencillo: tabla = common.GetGlobalConfigSection("TABLA") or {}El viejo truco de añadir "or {}" al final sirve para que si en el fichero no se encuentra la tabla pedida entonces se devuelva una tabla vacía, en vez de un valor nulo. Aunque útil, esta forma de proceder tiene el incoveniente de que el fichero puede llegar a tener un tamaño bastante grande si todos los addons se dedican a escribir en él de forma indiscriminada. Y de igual forma, con el paso del tiempo, puede llegar a contener mucha información inútil, ya que no se depura cuando se desinstalan los addons.
Juan Mellado, 1 Octubre, 2010 - 16:15
En este artículo se analiza un addon de Allods llamado "AutoSellGreyAddon". Su función es la de vender todos los objetos grises que lleva en la bolsa nuestro personaje, de forma automática, en el momento que se abra una ventana de diálogo con un vendedor. El autor utiliza el alias de Valltron, así que vayan para él todos los correspondientes créditos. El addon se compone de sólo dos ficheros: el obligatorio "AddonDesc.(UIAddon).xdb", y un script llamado "ScriptAutoSell.lua". No hay más. Ni formularios, ni imágenes, ni nada más. Aunque la verdad es que no lo necesita. El código del script empieza con la habitual llamada a la función "Init", que en ese caso se encarga de registrar dos eventos. El evento "EVENT_TALK_STARTED", que se lanza cuando se inicia una conversación con cualquier NPC del juego, no necesariamente vendedor. Y el evento "EVENT_TALK_STOPPED", que se lanza cuando se termina una conversación. function Init()Al iniciarse una conversación con un NPC el juego invoca a la función "OnTalkStarted", donde se registra el evento "EVENT_VENDOR_LIST_UPDATED", que es el que Allods lanza cuando un vendedor solicita a nuestro personaje la lista de objetos que tiene en la bolsa disponibles para la venta. Y por su parte, al terminar una conversación, se invoca a la función "OnTalkStopped" que elimina el registro de dicho evento "EVENT_VENDOR_LIST_UPDATED". function OnTalkStarted()Finalmente, la función "OnVendorListUpdated" es la que realmente hace todo el trabajo en una serie de pasos muy claros: function OnVendorListUpdated()La función "avatar.InventoryGetBaseBagSlotCount" es la que obtiene el número de objetos de la bolsa nuestro personaje. La función "avatar.GetInventoryItemId(slotIndex)" es la que obtiene el ID de un objeto concreto de la bolsa dada su posición dentro de la misma, que recordemos que puede ser de una casilla que está vacía, por lo que es necesario comprobar que ha devuelto un ID de un objeto válido. La función "avatar.GetItemInfo(itemId)" es la que obtiene el detalle de los atributos del objeto dado, siendo el atributo "quality" el que indica el nivel del objeto, aunque este es sólo uno de las varias decenas de atributos que devuelve la función y merecen la pena echar un vistazo en la documentación. Finalmente, la función "avatar.Sell(slotIndex, itemInfo.stackCount)" es la que vende el objeto de la bolsa dada su posición, y en su cantidad total además. Sin lugar a dudas este addon es muy claro ejemplo de sencillez y utilidad.
Juan Mellado, 24 Septiembre, 2010 - 16:23
Uno de los objetivos principales de los addons de Allods es sustituir la interface gráfica por defecto del juego por otra más personalizada que se adapte mejor a las necesidades de cada cada jugador, o que añada algún tipo de información u opción que facilite el desempeño de su rol. Allods trata todos los componentes de su interface de usuario como addons, incluidos los elementos propios internos que son parte del núcleo del juego, como el chat o el mapa por ejemplo. Y este tratamiento homogéneo es el que permite "descargar" un addon original del juego y sustituirlo por uno propio. Para obtener todos los addons cargados en el juego se debe utilizar la función "GetStateManagedAddons". Esta función devuelve una tabla Lua en la que cada elemento representa un addon. Por ejemplo, con el siguiente código se escribe en el log un listado con todos los addons cargados en el juego: ...En mi versión europea actual del juego, la 1.1.00.62, aparecen listados los siguientes 79 addons: Alchemy ContextShipDeviceLos nombres son bastante significativos, y como se observa, aparecen prácticamente todos los componentes internos del juego con algún tipo de interface gráfica. Cuando se carga un addon propio este también aparece en la lista precedido por "UserAddon". Así, si se carga el addon "SampleInit", que viene de ejemplo con el juego, aparece en el listado de la siguiente guisa: UserAddon/SampleInitConociendo los nombres de los addons, es posible cargarlos o descargarlos utilizando las funciones "StateLoadManagedAddon" y "StateUnloadManagedAddon" respectivamente. Aunque naturalmente no todos los addons pueden ser descargados, ya que algunos son demasiado básicos, y su ausencia dejaría el juego practicamente inutilizable. Para descargar o cargar un addon, como el de la brújula por ejemplo ("ContextCompass"), basta una simple línea de código: ...La idea de todo esto es que si queremos hacer un addon propio que sustituya la brújula original, lo primero que tendremos que hacer en el código Lua de nuestro addon será descargar el addon original para que no se solape con el nuestro. Sencillo, ¿no?. Dentro de los addons de ejemplo que vienen con el juego hay uno llamado "SampleZoneAnnounce" que sustituye al addon "ZoneAnnounce" original, que es el encargado de mostrar por pantalla los nombres de las zonas por las que van pasando los personajes. Si tenemos curiosidad por saber donde está el código Lua de los addons internos del juego, estos se encuentran en los ficheros "LuaCompiledSystem.pak" y "LuaCompiledIngame.pak" del directorio "data/Packs" donde se encuentra instalado el juego. Desgraciadamente están compilados y distribuidos en formato binario, por lo que no se pueden examinar. En versiones más antiguas del juego se distribuían en formato de texto y si se podían examinar.
Juan Mellado, 18 Septiembre, 2010 - 10:31
En el zip que contiene la documentación oficial, que se distribuye con el propio juego, hay un directorio llamado "ResourceSystem". Dentro de dicho directorio hay un ejemplo de cada tipo de fichero .xdb que puede usarse para crear addons para Allods, incluyendo todos los tipos de controles que pueden utilizarse dentro de los formularios con los que se implementan las interfaces gráficas. Desgraciadamente no hay mucha más documentación acerca de los controles y parámetros de configuración que admiten los formularios. Los ficheros que empiezan por "SampleDefault" contienen un ejemplo completo con todos los parámetros válidos para cado tipo de .xdb. Los ficheros que empiezan por "SampleDefaultExt" contienen además un ejemplo completo con todos los parámetros válidos para las opciones que admiten listas de valores. Son bastantes, y se echa de menos un IDE para construir formularios de forma gráfica y posteriormente exportarlos a .xdb. ¿Alguien se anima? En cualquier caso, en base a esos ejemplos, y aparte de los ya vistos para los textos y las texturas, los controles disponibles son los siguientes: - WidgetForm: Formulario. Este es el tipo que tienen las ventanas principales. Todos los addons tienen un fichero de este tipo para su formulario principal, independientemente de que luego implementen o no una interface gráfica. - WidgetPanel: Panel. Los formularios normalmente se componen de uno o más de tipo de controles, los paneles sirven para agruparlos e ir formando una jerarquía. - WidgetButton: Botón. Un clásico, poco más que añadir. - WidgetTextView: Texto. Otro que no merece más comentarios. - WidgetEditLine: Línea de edición. Este control presenta una línea de texto en la que se puede introducir valores manualmente por teclado. Permite configurar algunos aspectos básicos como el aspecto del cursor, la velocidad de parpadeo, o indicar si se va a utilizar para introducir passwords. En los ejemplos que vienen con el juego no se utiliza, y de hecho, ninguno de los tipos que siguen a continuación, por lo que prácticamente no existe ningún addon que implemente ninguno de estos tipos. Buscando por Internet apenas he encontrado un addon que utiliza este tipo, pero del resto que siguen ninguno. - WidgetTextContainer: Contenedor de texto. Parece que su objetivo obviamente es contener texto, pero resulta difícil precisar su funcionamiento. Debería ser el típico control en el que se puede ir añadiendo o quitando líneas de texto libre con formato, como en la salida de una ventana de chat por ejemplo. - WidgetScrollableContainer: Contenedor scrollable. Aparenta el típico control que se usa para contener a otros y que muestra una barra de scroll, horizontal o vertical, para mostrar los que quedan fuera. No queda claro si es necesario definir una barra de scroll propia o usa automáticamente una por defecto. - WidgetSimpleTable: Tabla. Este debería ser el tipo de control que permita contruir tablas con varias filas y columnas para listar registros. Sólo tiene un atributo llamado "tableStep" que parece hacer referencia al número de columnas deseadas. Aunque también podría ser un control para definir un layout en forma de tabla. - WidgetDiscreteScrollBar: Barra de scroll de valores discretos. Debe ser la típica barra de scroll que permite la elección de unos determinados valores concretos prefijados de antemano. - WidgetGlideScrollBar: Barra de scroll. He de suponer que de valores continuos, sin la restricción de valores prefijados de la anterior. Aunque de hecho tiene los mismos atributos que la anterior. En el API curiosamente no se encuentran funciones para este tipo de control, pero si para el anterior. - WidgetDiscreteSlider: Deslizador de valores discretos. Debería ser igual que las barras de scroll pero sin los botones de incremento y decremento, sólo el botón central. - WidgetGlideSlider: Deslizador. Igual que el anterior, pero para valores continuos. De igual forma que con las barras de scroll, en el API sólo hay funciones para el tipo de valores discretos. - WidgetConsole: Consola. Debería ser la típica ventana de introducción de comandos. Entre sus atributos hace referencia a un "EditLine", por lo que no queda claro que valor añadido tiene frente a una línea de introducción de textos ordinario, a menos claro que sea capaz de procesar los comandos introducidos. - WidgetConsoleOutput: Consola de salida. Aparenta ser la salida con los resultados de la ejecución de comandos introducidos por consola. Al igual que en el anterior tipo, entre sus atributos se hace referencia a un "textContainer", por lo que arroja algunas dudas acerca de su diferencia con una salida de texto ordinaria. - WidgetControl3D: Control 3D. Este tiene una pinta muy interesante, una pena que no exista documentación. Aparenta ser un control que permite visualizar una entidad del juego en tres dimensiones, como la ventana de información del personaje que muestra a nuestro avatar en miniatura e incluso nos permite rotarlo. El único addon que viene de ejemplo con el juego y que define un formulario algo elaborado es el llamado "SampleReactionHandler", aunque en la práctica apenas tiene un panel con una textura de fondo, dos textos y un botón. No obstante, aparte del propio formulario en si mismo, este addon tiene cosas interesantes. La primera cosa interesante es ver como se construye el botón, que hace uso del prototipo que se encuentra en el directorio "data/Mods/SampleCommon/Button". Dentro de ese directorio hay ni más ni menos que 23 ficheros (aunque los cuatro .tga originales no son realmente necesarios). Todos esos ficheros definen el aspecto de un botón en cada uno de los estados en los que puede estar: normal, seleccionado, presionado o deshabilitado. Y dan una idea de la enorme cantidad de trabajo que hace falta para definir completamente cualquier control. Realmente se echa en falta un IDE que simplifique el trabajo, o al menos una librería completa de este tipo de controles prototipo para usarlos como plantillas. La segunda cosa interesante de ese addon de ejemplo es ver el script Lua. Los controles registran reacciones en vez de eventos. ¿Y qué quiere decir esto en la práctica? Pues que en vez de subscribir el addon a eventos del mundo virtual utilizando la función "RegisterEventHandler", hay que utilizar la función "RegisterReactionHandler" para subscribirlo a reacciones de la interface gráfica. Poca cosa en realidad, simplemente que los desarrolladores decidieron diferenciar entre ambos tipos de sucesos. No existe un evento "botón presionado", sino que en cada "WidgetButton" se define el nombre de una "Reaction" y es a ese nombre al que se subscribe el addon. Por lo demás es igual, cuando se produce el suceso se llama a la función indicada en el script con los parámetros adecuados en función del tipo de suceso. Por último, comentar que toda la interface gráfica propia del juego utiliza estos mismos controles que podemos utilizar nosotros para crear nuestros propios addons. Desgraciadamente todos los ficheros .xdb de los formularios (personaje, correo, comercio, subasta, ...) no están includos dentro del juego en formato de texto, sino en formato binario, dentro del fichero pack.bin en "data/Packs/Bin.pack", y no se pueden examinar. |