inmensia |
MMORPG
Juan Mellado, 28 Agosto, 2010 - 08:07
Una característica específica de la implementación de Lua en Allods es que sólo están disponibles las librerías string, math, table y coroutine. Es decir, las que permiten manipular cadenas de texto, realizar operaciones matemáticas, gestionar tablas (arrays, lists, ...) e implementar multitarea. Y no se encuentran disponibles otras como file, io, os, ... que dan libre acceso a los recursos del sistema. Otra característica de la implementación de Lua que utiliza el juego es que no se permite el uso de las variables globales por defecto. Es decir, en Lua las variables automáticamente son globales, pero dentro de Allods esto no se permite, es necesario declararlas explícitamente de la forma "Global(nombre, valor)", como en el siguiente ejemplo: Global("counter", 0)En lineas más generales, comentar que los scripts de los addons se inicializan en el mismo orden que se encuentran listados dentro de la etiqueta <ScriptFileRefs> de sus correspondientes ficheros "AddonDesc.(UIAddon).xdb". Y se cargan automáticamente al iniciar el juego, o desde otro script, en función del valor de la etiqueta <AutoStart>. Aunque lo más importante sin lugar a dudas es tener claro que el funcionamiento de los addons se basa principalmente en la gestión de eventos. Es decir, los scripts dicen a qué sucesos que ocurren en el juego quieren reaccionar, e indican a cual función se les debe llamar cuando estos se produzcan. Allods adopta, en este sentido, el famoso modelo Hollywood: "No nos llame, ya le llamaremos". La lista de eventos disponibles es enorme, prácticamente para cualquier suceso que pueda ocurrir en el juego. Por ejemplo, para un personaje podemos programar un addon que reaccione cuando cambie de posición, cuando cambie su nivel de experiencia, cuando cambie la cantidad de dinero que tiene, cuando cambie su lista de amigos, cuando cambie de objetivo, cuando cambien sus talentos, ... y así un largo etcétera. Y lo mismo para las "parties", "raids", hermandades, objetos, inventarios, misiones, mapas, correo, ... Para registrar un manejador de eventos hay que realizar una llamada a "common.RegisterEventHandler(función, evento)", de forma que cuando ocurra el evento dado, Allods llamará automáticamente a la función indicada. En los addons de ejemplo que vienen con el juego hay uno muy sencillo llamado "SampleEventRegistration" que registra un evento de un temporizador que gestiona internamente el juego y que salta automáticamente cada segundo. El script "ScriptSampleEventRegistration.lua" de dicho addon contiene un código similar al siguiente: Global("counter", 0)El script declara un contador ("counter") en una variable global, para que su valor persista entre llamada y llamada, inicializándolo a cero. Registra una función ("OnEventSecondTimer") para que Allods la llame cada vez que salte el evento ("EVENT_SECOND_TIMER"). Y en cada activación motivada por la ocurrencia del evento incrementa el valor del contador y escribe su valor en el fichero de log por defecto, que recordemos se encuentra en el directorio "Personal\Logs" dentro de la carpeta donde se encuentra instalado el juego. Todas las funciones manejadoras de eventos reciben un único parámetro cuya interpretación depende enteramente del tipo de evento. Algo que resulta bastante natural en Lua, ya que es un lenguaje tipado dinámicamente. Así, para el evento del ejemplo, el parámetro no contiene ningún atributo. Pero para otros eventos, como el que salta cuando con nuestro personaje aceptamos una misión, por ejemplo, lo que se recibe es el identificador de la misión concreta que se ha aceptado, al que se accede con "params.questId". En la documentación se listan los eventos disponibles así como los parámetros concretos que recibe cada uno. De forma general, en los eventos asociados a misiones se reciben identificadores de misiones, en los referidos a personajes se reciben identificadores de avatares, y así sucesivamente. Estos identificadores son los que permiten realizar tareas concretas sobre los objetos del juego mediante la llamada a las funciones ofrecidas por el API de Allods. De igual forma que con los eventos, existen funciones para realizar prácticamente cualquier operación, excepto por supuesto para controlar los personajes. El API está pensado para reemplazar o añadir opciones a la interface de usuario del juego, o reaccionar a determinados estados de la lógica del juego, principalmente esto último para automatizar tareas como por ejemplo vender todos los objetos grises automáticamente cuando se abre un diálogo con un vendedor. La orientación a objetos de Lua permite escribir código ordinario en el que sobre un objeto se invoca un método con una serie de argumentos. Por ejemplo para que nuestro personaje acepte la realización de una misión dada basta con escribir la siguiente línea: avatar.AcceptQuest(questId)La variable "avatar" que aparece en el código anterior es global y se encuentra definida por defecto dentro de Allods y es accesible desde cualquier punto de cualquier script. Con ella podemos obtener información sobre el estado de nuestro personaje y realizar acciones con él. De igual forma se encuentran definidas otra serie de variables como "group", "raid", "mailBox", "guild", ... Hay que tener en cuenta que las variables globales siempre se encuentran instanciadas pero habrá muchas operaciones que no puedan hacerse con ellas si el personaje no se encuentra en el estado adecuado. Por ejemplo, no podrá abrir el correo si no se encuentra ante un buzón. Para ello hay funciones específicas que nos indican si los objetos encuentran en un estado adecuado para usarlas: LogInfo("mailBox: ", mailBox.IsReady() )No obstante, a estas alturas espero que haya quedado claro que la forma en la que se implementará normalmente los addons es respondiendo a los eventos correspondientes que nos avisarán cuando se está delante de un buzón, se ha recibido una invitación a un grupo, ... en vez de estar constantemente preguntando a las variables si se está en el estado adecuado.
Juan Mellado, 21 Agosto, 2010 - 08:45
Los addons de Allods son pequeños programas que se componen de las distintas partes de las que habitualmente consta una aplicación: interface de usuario, código de proceso, ficheros de recursos y librerías adicionales. El conjunto de estos ficheros es lo que se conoce como addon. Para crear un addon en Allods hay que crear un directorio dentro de la carpeta "data/Mods/Addons" donde se encuentra instalado el juego y poner en él todos los ficheros que forman el addon. Por ejemplo, si queremos crear un addon llamado "Prueba", debemos crear un directorio "data/Mods/Addons/Prueba" y poner en él todos sus ficheros. Aunque también es posible crearlos con varios niveles de directorios como "data/Mods/Addons/Pruebas/Prueba01". ¿Pero qué ficheros forman un addon en Allods? Pues lo mejor es verlo con un ejemplo real de un addon que viene con el juego que se llama "SampleInit". Se encuentra en el directorio "Mods/SampleAddons/SampleInit", y consta de tres ficheros: AddonDesc.(UIAddon).xdbEl primer fichero es obligatorio para todos los addons y tiene que tener siempre ese nombre fijo de "AddonDesc.(UIAddon).xdb". Es un XML en formato UTF-8 con una serie de etiquetas obligatorias que describe al addon. Veámoslas. La etiqueta <Name> contiene el nombre del addon. <Name>SampleInit</>La etiqueta <AutoStart> indica si el addon tiene que ejecutarse automáticamente al arrancar el juego. Es un simple valor booleano. <AutoStart>True</>La etiqueta <ScriptFileRefs> contiene la lista de ficheros con los scripts, escritos en Lua, que utiliza el addon. Cada entrada de la lista contiene el nombre de un fichero, que puede ser absoluto con respecto al directorio "data" del juego, o relativo a la ruta donde se encuentra el addon: <ScriptFileRefs>La etiqueta <Forms> contiene la lista de formularios que utiliza el addon. Cada elemento de la lista se compone de un identificador único y una referencia a un fichero .xdb donde se encuentra descrito el formulario correspondiente. Se utiliza la notación XPointer, que es una notación estándar que permite desde un fichero XML hacer referencia a una sección de otro fichero XML. <Forms>En el ejemplo de esta última etiqueta se define un formulario identificado como "Main" que se encuentra definido dentro de la sección "WidgetForm" de un fichero llamado "MainForm.(WidgetForm).xdb". La etiqueta <MainFormId> contiene el nombre del formulario principal del addon, que debe ser un identificador definido dentro de la lista contenida en la etiqueta <Forms>: <MainFormId>Main</MainFormId>De igual forma, es posible añadir otras etiquetas predefinidas, o incluso propias, como el nombre del autor, la versión de addon o la fecha de última actualización, aunque en la documentación no se indica más detalle acerca de ellas. El fichero "AddonDesc.(UIAddon).xdb" de este addon "SampleInit", al completo, contiene lo siguiente: <?xml version="1.0" encoding="UTF-8" ?>A estas alturas ya debería adivinarse el patrón que sigue el nombre de los ficheros .xdb, con un título (AddonDesc, MainForm, ...) seguido de un tipo (UIAddon, WidgetForm, ...) Estos tipos están predefinidos y sirven para indicar la clase de información que contiene el fichero, que además de información en formato de texto también admite ficheros de recursos binarios como pueden ser texturas. A continuación el fichero "MainForm.(WidgetForm).xdb" correspondiente a este mismo addon y que resulta bastante fácil de entender leyendo entre líneas: <?xml version="1.0" encoding="UTF-8" ?>Y por último, el contenido del fichero "ScriptSampleInit.lua" de este mismo addon que se limita a dejar constancia de su arranque en el fichero de log predefinido por el juego que se llama "mods.txt" y que se encuentra en el directorio "Personal/Logs": -----------------Para probar este simple addon de ejemplo basta con copiar la carpeta "SampleInit" de "data/Mods/SampleAddons" a "data/Mods/Addons". Al arrancar el juego aparecerá un nuevo botón, en la ventana de login, que nos permite activar o desactivar los addons, que por defecto se encuentran desactivados. Al entrar en el juego propiamente dicho, después de la ventana de selección de personaje, veremos como se graba el mensaje en el fichero de log, señal de que el addon se encuentra correctamente instalado y funcionando.
Juan Mellado, 14 Agosto, 2010 - 07:55
Como suelo hacer de vez en cuando, sobre todo después de unas largas vacaciones, he decidido hacer una serie de posts dedicados a un tema concreto, y esta vez le ha tocado a los "addons". Más concretamente a los de Allods Online. Un MMORPG gratuito que conocí hace un par de meses en un post que leí en Planet Stratos. El juego tiene la clásica ambientación fantástico-medieval con la mecánica y los elementos habituales de este tipo de juegos. Un par de facciones enfrentadas, unas cuantas razas por facción, y varias clases con árboles de talentos para tanqueo, sanación y daño. Misiones de "grinding" y "farming", algunas cuantas instancias para ir de "party" o "raid", profesiones, reputaciones y casa de subastas. Los desarrolladores son rusos, y eso se nota en algunos aspectos del juego, hay muchos NPCs con nombres como "Boris" o "Andrei", e incluso una región que se llama "Siveria". Aunque lo realmente curioso es que tienen la política de implantar las nuevas versiones primero en Rusia y luego en el resto de Europa y América. Es decir, que jugamos con un par de parches por debajo de la versión oficial rusa. Es más, por lo que he podido entender, la posibilidad de usar addons es algo que se ha añadido en el parche actual, o quizás en el anterior. Perfecto para mis propósitos. El juego, como ya he comentado, es gratuito, y esto quiere decir que se puede crear una cuenta, un personaje y jugar todo lo que se quiera, sin restricciones. Nuevamente perfecto para mis propósitos. Eso sí, existe la posibilidad de adquirir objetos que hacen la vida más fácil, como una bolsa con más casillas por ejemplo, pagando una pequeña cantidad de dinero. Pero es algo completamente opcional. Aunque hay otra serie de objetos que también se venden y sirven para potenciar el daño y la sanación, hasta un 100%, que mucha gente considera algo necesario tener, ya que si no el juego se hace muy difícil y el "leveling" (hasta el nivel 42) muy lento. Desgraciadamente el juego no está traducido al español, y creo que no existen planes para hacerlo en un futuro inmediato. De hecho, la documentación de los addons ni siquiera está traducida al inglés. Y es que la única información técnica oficial disponible es la que se incluye junto con la descarga del mismo juego, ¡y está en ruso!. Dentro del directorio de instalación del juego, en la carpeta "data\Mods\Docs". Son unas páginas HTML con una breve guía de construcción e instalación, así como una descripción de todas y cada una de las funciones disponibles del API. Ya que aunque todavía no lo he comentado, los addons se escriben utilizando Lua, uno de los lenguajes de scripts más utilizados para este tipo de tareas. Bueno, y llegado este punto, es hora de abrir un traductor de ruso y ponerse a leer un rato.
Juan Mellado, 27 Diciembre, 2008 - 11:34
Coincidiendo con el fin de año llega también el fin de esta serie de posts. Del modelo de datos de PlaneShift quedan todavía algunas tablas por examinar, pero las principales creo que se han examinado todas, o al menos las suficientes como para entender como está estructurado en términos bastantes generales el juego en lo que refiere a esta parte concreta. Queda un primer grupo de tablas por examinar relacionadas con el concepto de "superclient", un término que utilizan en este juego para referirse a la gestión que realizan de los NPCs, mediante procesos ejecutándose en máquinas conectadas al servidor como si fueran jugadores normales. Otro segundo grupo de tablas que contiene información variada sobre el juego, como recursos naturales, zonas de caza, y cosas por el estílo. Y un tercer grupo que almacena opciones de configuración del servidor, comandos de gm (game master), mensajes de ayuda, etc... Llegado este punto, si a alguien le interesa profundizar más en el funcionamiento del juego, le recomiendo que continue examinando el detalle concreto de cada tabla, estudiando a la par el modelo y el código fuente. Pero esa no es una opción que me atraiga mucho. Lo tomé sólo como modelo de un ejemplo real totalmente operativo, pero no quiere decir que sea el camino a seguir en todos los casos. Lo importante ha sido comprobar que realmente no hay mucha diferencia entre una aplicación de gestión y un juego de estas características, al menos en lo que al diseño de la base de datos se refiere. Es lo que tiene basarse en un gestor relacional, al final tiene que acabarse haciendo un modelo relacional, dando igual que sea para una aplicación de contabilidad que para un MMORPG. Echando la vista atrás, y releyendo un poco por encima toda esta serie, he recordado las dudas que tenía al principio sobre la viabilidad del uso de una base de datos convencional para este tipo de juegos. Sobre todo después de leer que algunas compañías habían optado por desarrollar su propio gestor de transacciones. O que otras, a pesar de utilizar un gestor comercial, habían decidido saltarse cualquier tipo de normalización. Supongo que cuando se espera tener millones de cuentas y los objetivos son muy elevados, casi extremos, muchas de esas soluciones cobrarán sentido. Pero creo que sería algo para analizar en detalle para cada caso concreto. Yo personalmente optaría primero por tener un modelo totalmente normalizado, y luego ir desnormalizando a medida que haga falta. Aunque de todas formas hoy en día las mejoras de rendimiento muchas veces se prefieren obtener sustituyendo el hardware, algo generalmente más barato y menos complejo que realizar grandes cambios en el software. Las limitaciones que tradicionalmente se le han achacado a las base de datos relaciones, en cuanto a escalabilidad se refiere, se están dejando atrás por el aumento de prestaciones del hardware y la disminución de su coste, además del uso de otras técnicas como el particionado de tablas por ejemplo. El siguiente paso lógico, después de la elaboración del modelo, sería contruir la capa de abstracción para el acceso a la base de datos. Normalmente un conjunto de clases a modo de wrapper sobre el API nativo proporcionado por el gestor utilizado. En PlaneShift estas clases están implementadas en los ficheros dal.cpp y dal.h (DAL = Data Access Layer), suministrando los habituales métodos de conexión, desconexión y ejecución de sentencias SQL. Si la elaboración del modelo es ya de por si un mundo, la construcción de una capa de abstracción no se queda atrás. Para sacar el máximo partido posible de las capacidades de una base de datos se debe optar generalmente por estudiar a fondo las posibilidades que ofrece esta. La ejecución de una simple sentencia puede ser algo rápido y casi inmediato, pero la ejecución de miles ya no tanto. La utilización de sentencias precompiladas, el uso de caches intermedias, la programación en algún tipo de PL/SQL soportado por el gestor, o las facilidades para las inserciones masivas (bulk arrays) son sólo algunas de las estrategías básicas que se deberían tener en cuenta. Aunque naturalmente, en vez de construir una capa de persistencia propia, se puede optar por la utilización de algún tipo de framework ya construido a tal efecto. Es dificil a priori decir que enfoque será el más eficiente de cara a la implementación de esta parte. Lo mejor como siempre será probar varias técnicas y evaluarlas de forma individual mediante las oportunas pruebas de volumen y rendimiento. Si se espera que una tabla tenga miles de registros es mejor probarla con un millón, y si se espera que tenga un millón probarla con varios de ellos. Conocer en este punto las características particulares de almacenamiento proporcionado por el gestor puede ayudar a decidir como guardar los datos, optimizando la creación de los índices, particiones y las formas de acceso. En este punto también es bueno construir pequeñas herramientas auxiliares para la generación automática de registros. Las base de datos suele proporcionar meta-información acerca de la misma, de forma que a través de una consulta SQL normal se puede recuperar el nombre de las tablas, de las columnas, y sus tipos. Con esa información es fácil hacer un programa que inserte de forma automática registros con información aleatoria con la que rellenar facilmente una tabla. Incluso existe software comercial especializado en ese tipo de tareas; muy útil para realizar "demos" con nombres, direcciones y teléfonos de clientes ficticios, por ejemplo. La actualización diferida de la base de datos es otro punto que quizás debería tenerse en cuenta desde el principio. Es lógico pensar que toda sentencia a ejecutar acabe teniendo una prioridad. No deberá ser lo mismo actualizar el modelo para reflejar un daño inmediato que está sufriendo un personaje, que actualizar la dirección en la que mira. En algunos casos puede que realmente ambas tengan la misma prioridad, en otros no. Además, si los cambios afectan a columnas de una misma tabla, de un mismo registro, entonces realizar las dos actualizaciones al unísono puede ser incluso mejor. Y todo ello sin perder de vista que lo importante es conseguir que el estado del mundo virtual quede siempre coherentemente reflejado en la base de datos. Y en fin, esta es sólo la punta del iceberg. Más allá de la base de datos queda una lista enorme de cosas por hacer. Posiblemente lo siguiente sería estudiar la conectividad entre los clientes y el servidor, o servidores, o entre los propios clientes entre sí (para un chat de voz por ejemplo). Decidir que protocolo de red usar (la eterna discución TCP vs UDP). E incluso definir un protocolo propio para el intercambio de mensajes tratando de minimizar el volumen de información intercambiada (usando coordenadas relativas por ejemplo). Y así un largo, largo, largo, etcétera.
Juan Mellado, 20 Diciembre, 2008 - 09:55
En los juegos de ambientación medieval, como es el caso de PlaneShift, es bastante normal que se permita a los jugadores desarrollar algún tipo de profesión mediante la cual puedan crear objetos propios empleando elementos clásicos de esta época, como yunques o forjas por ejemplo. Estas profesiones, así como los objetos que pueden crearse, están también almacenados en base de datos. Por un lado hay un primer grupo de tablas donde están las habilidades que pueden desarrollarse, junto con los instructores que las enseñan, y por otro lado hay un grupo de tablas con los procesos y patrones (recetas, fórmulas, ...) disponibles. Me ha llamado más la atención este segundo grupo, así que este artículo sólo toca esa parte en concreto. Algunas de las tablas de base de datos referidas a este tema son de las pocas que están comentadas, aunque sólo dentro de los propios scripts de creación, los comentarios no forman parte de la definición de las mismas. ![]() Una "transformación" (transformation) define como un jugador puede crear un nuevo objeto a partir de otro. Cada transformación se compone de un "patrón" (pattern) y de un "proceso" (process). Por ejemplo, una transformación podría consistir en la creación de una espada mediante la fundición y posterior tratamiento de varias menas de hierro. La tabla de transformaciones en base de datos tiene dos primeras referencias, una a la tabla de patrones y otra a la tabla de procesos. A continuación sigue un referencia a la tabla de objetos, para indicar que tipo de objeto se crea, y el número de ellos que se genera cada vez. Después sigue una referencia al tipo de objeto a transformar, junto con el número requerido de ellos. Luego está la cantidad de tiempo necesaria para completar la transformación (aunque la columna se llama "points"), un porcentaje a aplicar sobre la calidad del objeto creado, y cierra la típica columna de descripción. La tabla de patrones por su parte tiene varias columnas de significado claro, como el ID para la clave primaria, el nombre corto de la receta, la descripción larga, una referencia a la tabla de objetos para indicar cual de ellos permite crear, y un ID de grupo para los patrones de un mismo tipo. Sin embargo, tiene otra columna llamada "k_factor" cuyo significado no queda claro atendiendo sólo a su declaración. Puede estar relacionada con las "Knowledge Areas", o "áreas de conocimiento" que tiene el juego y mediante las cuales se controlan las distintas habilidades necesarias para el desarrollo de profesiones. Otra columna de significado un tanto confuso es la del tipo de objeto generado, ya que es opcional, por lo que no se ha interpretar al pie de la letra. Es decir, un mismo patrón se puede utilizar para crear más de un tipo de objeto. No todo patrón corresponde con una única receta, diseño o fórmula. El ejemplo claro de esto es el patrón llamado "Baking" (hornear) que está asociado a un montón de transformaciones culinarias distintas. La tabla de procesos tiene un número de "subproceso", para la elaboración de objetos en varias fases. Un par de columnas con los nombres de la animación y el efecto gráfico a mostrar mientras se elabora el objeto. Una referencia al objeto necesario para llevar a cabo el proceso, como un yunque por ejemplo. Y otra referencia más a la tabla de objetos para indicar esta vez el tipo de objeto que debe llevar equipado el personaje, como un martillo de herrero por ejemplo. A continuación sigue una referencia a una tabla de "restricciones" (constraints), que son condiciones especiales que deben darse para la ejecución del proceso. Una nueva referencia a la tabla de objetos señala a un tipo de objeto "basura" (residuo) que se genera si la transformación no se produce correctamente, junto con el número que de estos objetos pueden llegar a generarse. Después hay dos grupos de cinco columnas cada uno, las cinco primeras relacionadas con la habilidad primaria que debe poseerse para realizar la transformación, y las cinco segundas, de idéntico significado, para una posible habilidad secundaria. Estas columnas incluyen una referencia a la tabla de habilidades, un valor mínimo y máximo de habilidad requerida, un valor de puntos de aprendizaje ganados por el personaje al realizar la transformación, y un porcentaje que afecta a la calidad del objeto de forma directamente proporcional a la habilidad del personaje que lo crea. Cierra de nuevo la tabla la típica columna de descripción. La tabla de restricciones almacena las condiciones que deben darse para que los jugadores puedan ejecutar los procesos. Es una tabla muy sencilla, y en los registros de ejemplo se observa que son restricciones de tipo temporal ("You can not complete the work at this time of the day!"), de ubicación ("You can not finish this work here!"), y así sucesivamente. En el campo de descripción de esta tabla se indica que se pueden indicar parámetros concretos, como una hora del día o lugar concreto en el que tiene que estar el personaje. Una "combinación" (combination) define como un jugador puede crear un nuevo objeto mediante la agrupación de una cantidad determinada de otros. Es similar a una transformación, con la diferencia que no aplica un proceso, sólo un patrón. Las columnas de la tabla de combinaciones son muy similares a las de la tabla de transformaciones. Patrón, objeto resultante, cantidad resultante, objeto de partida, mínima y máxima cantidad de objetos de partida, y una descripción. La última tabla del modelo de esta parte almacena lo que se denominan "autocontenedores" (autocontainers). Un nombre un tanto extraño quizás. Mirando los registros de ejemplo se observa que contiene los objetos utilizados para crear otros, como por ejemplo los yunques y las forjas. La columna más importante a mi juicio es la que hace referencia a la tabla de objetos instanciados en el mundo virtual, lo que define su ubicación concreta dentro del mundo, junto con el resto de atributos que tienen los objetos en general. Es decir, que esta tabla se utiliza para modelar físicamente la herencia, como una clase especializada de objetos. |