MMORPG

Juan Mellado, 4 Septiembre, 2010 - 09:41

Aunque es posible escribir addons para Allods que sólo contengan código, como los ejemplos vistos hasta ahora, lo normal es incluir algún tipo de texto o gráfico. La documentación oficial dice al respecto que, además de ficheros con extensión .xdb y .lua, se pueden incluir también ficheros con extensión .txt y .bin como parte de un addon.

Los ficheros .txt deben contener los textos que se quieran mostrar a los jugadores, y deben estar escritos en formato UTF-16LE. Usar este formato permite utilizar todo tipo de juego de caracteres especiales para cada idioma, como el cirílico por ejemplo. Colocar los textos en un fichero independiente facilita su rápida localización por parte de cualquier persona, sin tener que andar revisando y modificando el código fuente de los scripts Lua.

La documentación es un poco parca con respecto al formato de los ficheros de texto, así que lo mejor es echar un vistazo a los addons que vienen con el juego. Por ejemplo, en el directorio del addon "SampleReactionHandler" pueden verse tres ficheros con extensión .txt: "ButtonExecute.txt", "Header.txt" y "Text.txt". El primero se corresponde al texto que se muestra en un botón, y sólo contiene el texto "Ejecutar" (los textos originales están en ruso). Los otros dos contienen los textos que se muestran en el título y cuerpo de una ventana respectivamente. No obstante, la diferencia de estos dos últimos con respecto al primero es que contienen una etiqueta XML con un atributo para indicar la alineación horizontal deseada. Por ejemplo, el fichero "Header.txt" contiene lo siguiente:

<header alignx="center">Título</header>

Como ya he comentado, no hay mucha información acerca de las etiquetas y atributos permitidos, aunque buscando con un poco de imaginación en la documentación del API de Lua para Allods se pueden encontrar algunos. Por ejemplo, en la función "SetPlacementPlain", que sirve para cambiar la posición de un control, se pueden ver atributos de posicionamiento y alineamiento.

Además de los textos prefijados que aparecen en los controles de la interface gráfica, como los botones, también es posible definir una serie de textos libres utilizando un fichero .xdb de tipo "UIRelatedTexts". Esta clase de ficheros contienen una simple lista en el que cada elemento tiene un nombre y una referencia a un fichero de texto. Sirva de ejemplo un fichero, al que llamaré "Texts.(UIRelatedTexts).xdb", con el siguiente contenido:

<?xml version="1.0" encoding="UTF-8" ?>
<UIRelatedTexts>
  <items>
    <Item>
      <name>Texto01</name>
      <resource href="Texto01.txt" />
    </Item>
    <Item>
      <name>Texto02</name>
      <resource href="Texto02.txt" />
    </Item>
  </items>
</UIRelatedTexts>

El fichero de ejemplo contiene una lista de dos elementos. No obstante, para que esos dos textos sean visibles dentro del addon es necesario añadir una referencia a ellos en "AddonDesc.(UIAddon).xdb":

...
<texts href="Texts.(UIRelatedTexts).xdb" />
...

Y de esta forma desde el código de un script Lua se podrá obtener el texto de cualquiera de ellos por su nombre utilizando la función "common.GetAddonRelatedText(nombre)":

...
texto = common.GetAddonRelatedText("Texto01")
...
texto = common.GetAddonRelatedText("Texto02")
...

En otro de los addons que vienen de ejemplo con el juego, el llamado "SampleZoneAnnounce", se puede ver un fichero "Announce.txt" que contiene lo siguiente:

<header color="0xFFCC1111" alignx="center" fontsize="36" shadow="1">
<rs class="class">
  <r name="value"/>
</rs>
</header>

En este ejemplo se establece el color, el alineamiento, tamaño de fuente y efecto de sombreado. Establecer este tipo de atributos en ficheros separados de los scripts también es una estrategia acertada, ya que permite modificar la presentación sin tener que tocar la lógica de proceso. Otra curiosidad de este ejemplo es que no utiliza una cadena de texto fijo, sino que se deja abierto para que el valor a mostrar en el control se establezca desde el código de un script en tiempo de ejecución.

Naturalmente, cuando uno empieza a trastear con este tipo de cosas, lo que a uno le interesa es mostrar un texto por pantalla y cambiarlo de forma dinámica. Para ver como se hace esto es mejor recurrir de nuevo al addon de ejemplo "SampleZoneAnnounce". Lo primero que hace es añadir en el fichero "MainForm.(WidgetForm).xdb", que describe el formulario principal del addon, una etiqueta de texto hija llamada "Announce":

...
<Children>
  <Item href="Announce.(WidgetTextView).xdb#xpointer(/WidgetTextView)" />
</Children>
...

Todos los controles, ya sea el formulario principal o alguno de sus hijos, tienen que tener su propio fichero .xdb que lo describa. Y así, el control con la etiqueta de texto hija se describe en el fichero "Announce.(WidgetTextView).xdb". Como se observa, se utiliza el tipo "WidgetTextView" para indicar que es un control de texto, a diferencia del formulario principal que utiliza el tipo "WidgetForm". Existe un tipo distinto para cada control, y mediante la referencia de unos dentro de otros de forma jerárquica se pueden contruir interfaces bastantes elaboradas.

El siguiente paso es añadir en el fichero "Announce.(WidgetTextView).xdb" una referencia al fichero que contiene el texto que debe mostrarse en la etiqueta:

...
<FormatFileRef href="Announce.txt" />
...

Como se observa, se utiliza una etiqueta llamada "FormatFileRef". Es decir, que a lo que se hace referencia es a un fichero de formato, no a un simple fichero de texto, aunque su extensión haga pensar lo contrario. Algo totalmente coherente con el contenido del fichero, que como ya se ha visto, contiene tanto la cadena de texto como el formato que tiene que aplicársele.

El último paso es obtener una referencia al control hijo "Announce" de tipo etiqueta declarado en el formulario principal, y cambiar su valor de forma dinámica, en el script "ScriptSampleZoneAnnounce.lua":

...
wtMessage = mainForm:GetChildChecked("Announce", false)
...
wtMessage:SetVal("value", "Texto que se quiere mostrar")
...

La variable "mainForm" es global y la instancia automáticamente el juego, no es necesario que lo hagamos nosotros. La función "GetChildChecked" obtiene una referencia al control cuyo nombre se pasa como primer argumento. El segundo argumento indica si se tiene que buscar recursivamente por toda la jerarquía de controles (true) o sólo los hijos directos al padre dado (false). La función "SetVal" es la que cambia el valor del texto que se muestra en el control.

Como último comentario, para que los que como yo sean nuevos en esto de Lua, decir que usar los dos puntos (:) en vez del punto (.) en una expresión de la forma "objeto:funcion(parametros)" es equivalente a "objeto.funcion(self, parametros)". Es decir, es la manera de invocar "métodos" sobre objetos concretos en vez de invocar "funciones" de librerías. El primer parámetro de un "método" es un argumento explícito llamado "self" al que Lua le asigna el valor del objeto sobre el que se realiza la llamada automáticamente cuando se utiliza la notación de los dos puntos.

Juan Mellado, 28 Agosto, 2010 - 08:07

LuaEn la documentación oficial de los addons para Allods hay unos pocos apuntes técnicos acerca de como se ejecutan los scripts de Lua dentro del juego. Como por ejemplo que se utiliza la versión 5.0.x, o que para cada addon se levanta su propia máquina virtual independiente, aunque existen técnicas para intercambiar información entre ellos mediante el uso de unos eventos especiales.

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)
--------------------------
function OnEventSecondTimer(params)
  counter = counter + 1
  LogInfo("counter: ", counter)
end
--------------------------
function Init()
  common.RegisterEventHandler(OnEventSecondTimer, "EVENT_SECOND_TIMER")
end
--------------------------
Init()

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() )
LogInfo("group: ", group.GetLeaderIndex() )
LogInfo("raid: ", raid.IsExist() )
LogInfo("guild: ", guild.IsExist () )

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).xdb
MainForm.(WidgetForm).xdb
ScriptSampleInit.lua

El 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>
  <Item href="/Mods/SampleCommon/SampleAddonBase.lua" />
  <Item href="ScriptSampleInit.lua" />
</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>
  <Item>
    <Id>Main</Id>
    <Form href="MainForm.(WidgetForm).xdb#xpointer(/WidgetForm)"/>
  </Item>
</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" ?>
<UIAddon>
  <Name>SampleInit</Name>
  <AutoStart>true</AutoStart>
  <ScriptFileRefs>
    <Item href="/Mods/SampleCommon/SampleAddonBase.lua" />
    <Item href="ScriptSampleInit.lua" />
  </ScriptFileRefs>
  <MainFormId>Main</MainFormId>
  <Forms>
    <Item>
      <Id>Main</Id>
      <Form href="MainForm.(WidgetForm).xdb#xpointer(/WidgetForm)" />
    </Item>
  </Forms>
  <visObjects href="" />
  <texts href="" />
  <textures href="" />
</UIAddon>

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" ?>
<WidgetForm>
  <Name>SampleInit</Name>
  <Priority>0</Priority>
  <Children>
  </Children>
  <BackLayer href="" />
  <FrontLayer href="" />
  <Placement>
    <QuantumScale>false</QuantumScale>
    <X>
      <Align>WIDGET_ALIGN_LOW</Align>
      <Sizing>WIDGET_SIZING_DEFAULT</Sizing>
      <Pos>0</Pos>
      <HighPos>0</HighPos>
      <Size>0</Size>
    </X>
    <Y>
      <Align>WIDGET_ALIGN_LOW</Align>
      <Sizing>WIDGET_SIZING_DEFAULT</Sizing>
      <Pos>0</Pos>
      <HighPos>0</HighPos>
      <Size>0</Size>
    </Y>
  </Placement>
  <Visible>true</Visible>
  <Enabled>true</Enabled>
  <TransparentInput>true</TransparentInput
  <PickChildrenOnly>false</PickChildrenOnly>
</WidgetForm>

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":

-----------------
-- INITIALIZATION
-----------------
function Init()
  LogInfo("Initialization sample")
end
-----------------
Init()
-----------------

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

Allods OnlineLos "addons" son extensiones que permiten añadir opciones nuevas a un programa o modificar el comportamiento de las ya existentes. Hoy en día es bastante normal que todo tipo de aplicaciones los soporten, incluidos los juegos. Y supongo que ya estamos tan acostumbrados a disponer de esta posibilidad que lo consideramos como un hecho ordinario, aunque creo que a mi siempre me seguirá pareciendo algo extraordinario eso de poder meternos en la piel de un programa para modificarlo casi a nuestro antojo de una forma limpia y elegante.

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.