Monday, March 25, 2019

Mientras tanto, en Plutón...

Seguimos con el análisis de la definición de objetos que empezamos en la anterior entrada. En esta ocasión veremos los atributos más "normales" que no estaban en la versión primitiva del motor, dejando la definición de acciones y disparadores para la próxima entrada.

Esta es CASI mi forma final

La clase gameItem tiene un pié puesto en la versión final, con la excepción de un par de atributos relacionados con el ruido que puede hacer un objeto y una posible revisión de cómo funciona el empujar, tirar y mover objetos, claro.
  • inventariable. Un valor booleano que indica si el objeto puede ser añadido al inventario del jugador.
  • movable, pushable, pullable. Valores booleanos que indican si un objeto si el jugador puede tirar de un objeto, empujarlo o moverlo. Actualmente movable no se utiliza y pushable y pullable se usan como condición necesaria para ejecutar las acciones "empujar" y "tirar".
  • weight. Un valor entero que indica el peso del objeto. A efectos prácticos el peso indica el espacio que el objeto ocupa en el inventario, de modo que el jugador no podrá llevar en su inventario objetos cuyo peso supere el límite de inventario definido en adventure.json.
  • lightSource. Un valor entero que indica el nivel de luz emitido por el objeto. Pueden echarle un vistazo al funcionamiento de las fuentes de luz en esta entrada de la serie.
  • noisy, noiseDescription. Actualmente pendientes de implementación. La idea es que el atributo noisy indique si el objeto hace ruido y noiseDescription se añada a la descripción de la localización si hay un objeto ruidoso en la localización o en una localización adyacente.
  • visibility. Un valor entero que indica el valor mínimo que deben tener las fuentes de luz presentes en la localización para que el personaje pueda ver el objeto. El jugador no podrá interactuar con aquellos objetos que no pueda ver.
  • image, largeImage. Los nombres de los archivos de imagen que se usarán en las listas de objetos de inventario y de objetos en la localización actual en el caso de image o en la vista de detalle del objeto a pantalla completa, si se usa, en el caso de largeImage.
  • implicit. El funcionamiento del atributo implicit se explicó en la primera entrada de esta serie. Los objetos implícitos serán aquellos que forman parte del inventario del personaje, pero no ocupan espacio, no se muestran en el panel de objetos de inventario -ya hablaremos sobre este panel cuando lleguemos a la interfaz de usuario- y el personaje no podrá interactuar con ellos de la misma manera que con los objetos no implícitos.
Como ven, nada demasiado complicado. Por ahora.

Monday, March 18, 2019

Mientras tanto, en Plutón...

Si las localizaciones son la columna vertebral de una aventura los objetos son, no sé, el fluido linfático. Sí, por qué no.

Objetos orientados a objetos

Como ya hicimos con la definición de las localizaciones, vamos a comparar cómo se definían los objetos en la versión primitiva y actual del motor.

En tiempos remotos los objetos se definían en el archivo game_items.xml, que tenía este aspecto:

<items>
   <item>
      <itemId>1</itemId>
      <itemName>Pila</itemName>
      <itemDescription>Una pila eléctrica, un místico artefacto capaz de dar energía a ciertos objetos.</itemDescription>
      <itemLocationText> En el suelo puedes ver una pila.</itemLocationText>
      <itemType>0</itemType>
      <itemLocation>10</itemLocation>
      <characterDialog> </characterDialog>
   </item>
   <item>
      <itemId>2</itemId>
      <itemName>Linterna</itemName>
      <itemDescription>Una linterna, es capaz de dar luz en lugares oscuros... si dispusiese de una fuente de energía.</itemDescription>
      <itemLocationText> En el suelo puedes ver una linterna.</itemLocationText>
      <itemType>0</itemType>
      <itemLocation>0</itemLocation>
      <characterDialog> </characterDialog>
   </item>
...
</items>

Una definición bastante sencilla con un identificador único, un nombre, dos descripciones -una, itemDescription, para cuando el objeto se examina y otra, itemLocationText, para añadir al texto descriptivo de la localización en la que está el objeto-, un identificador de tipo de objeto -en este caso 0, correspondiente a los objetos que pueden ser añadidos al inventario-, el identificador de la localización en la que se encuentra el objeto y el diálogo que se mostrará cuando el jugador hable con el objeto porque de aquella los personajes se consideraban objetos y sólo tenían una línea de diálogo posible. Sí eran tiempos oscuros.

Ahora la definición de los objetos va en el archivo game_items.json y es considerablemente más densa. Los mismos objetos "pila" y "linterna" tienen ahora este aspecto:

[
  {
    "id":1,
    "names":["Linterna"],
    "description":"Una linterna, es capaz de dar luz en lugares oscuros... si dispusiese de una fuente de energía.",
    "onLocationDescription":"En el suelo puedes ver una linterna.",
    "itemListDescription":"Una linterna, es capaz de dar luz en lugares oscuros... si dispusiese de una fuente de energía.",
    "location":0,
    "inventariable":"true",
    "movable":null,
    "pushable":null,
    "pullable":null,
    "weight":1,
    "lightSource":0,
    "noisy":null,
    "noiseDescription":null,
    "onPickTriggers":null,
    "onDropTriggers":null,
    "onActionTriggers":[
        {
            "id":1,
            "triggerType":1,
            "triggerSubType":0,
            "beenTriggered":"false",
            "actionId":18,
            "locationId":null,
            "itemId":1,
            "characterId":null,
            "enabled":"true",
            "effects":[
              {
                "id":1,
                "type":1001,
                "passiveGameItems":null,
                "passiveGameLocations":null,
                "passiveGameNPCs":null,
                "passiveGameTriggers":null,
                "passiveGameFlags":null,
                "newIntegerValue":null,
                "newBooleanValue":null,
                "newStringValue":"Enciendes la linterna. Un haz de luz emerge de uno de sus extremos.",
                "message":null
              },
              {
                "id":2,
                "type":131,
                "passiveGameItems":[1],
                "passiveGameLocations":null,
                "passiveGameNPCs":null,
                "passiveGameTriggers":null,
                "passiveGameFlags":null,
                "newIntegerValue":2,
                "newBooleanValue":null,
                "newStringValue":null,
                "message":null
              }
            ],
            "conditionalFlags":[
              {
                "id":1,
                "name":"Pila en linterna",
                "type":1,
                "booleanValue":"true",
                "integerValue":null
              }
            ],
            "activeItemConditions":null,
            "passiveItemConditions":null
        },
        {
            "id":2,
            "triggerType":1,
            "triggerSubType":0,
            "beenTriggered":"false",
            "actionId":19,
            "locationId":null,
            "itemId":1,
            "characterId":null,
            "enabled":"true",
            "effects":[
              {
                "id":1,
                "type":1001,
                "passiveGameItems":null,
                "passiveGameLocations":null,
                "passiveGameNPCs":null,
                "passiveGameTriggers":null,
                "passiveGameFlags":null,
                "newIntegerValue":null,
                "newBooleanValue":null,
                "newStringValue":"Apagas la linterna.",
                "message":null
              },
              {
                "id":2,
                "type":131,
                "passiveGameItems":[1],
                "passiveGameLocations":null,
                "passiveGameNPCs":null,
                "passiveGameTriggers":null,
                "passiveGameFlags":null,
                "newIntegerValue":0,
                "newBooleanValue":null,
                "newStringValue":null,
                "message":null
              }
            ],
            "conditionalFlags":[
              {
                "id":1,
                "name":"Pila en linterna",
                "type":1,
                "booleanValue":"true",
                "integerValue":null
              }
            ],
            "activeItemConditions":null,
            "passiveItemConditions":null
        }
    ],
    "onExamineTriggers":null,
    "visibility":1,
    "image":"item.png",
    "largeImage":null,
    "implicit":false
  },
  {
    "id":2,
    "names":["Pila eléctrica","pila electrica","pila"],
    "description":"Una pila eléctrica.",
    "onLocationDescription":"Puedes ver una pila.",
    "itemListDescription":"Una pila eléctrica.",
    "location":10,
    "inventariable":"true",
    "movable":null,
    "pushable":null,
    "pullable":null,
    "weight":1,
    "lightSource":0,
    "noisy":null,
    "noiseDescription":null,
    "onPickTriggers":null,
    "onDropTriggers":null,
    "onActionTriggers":[
      {
        "id":1,
        "triggerType":1,
        "triggerSubType":0,
        "beenTriggered":"false",
        "actionId":16,
        "locationId":null,
        "itemId":1,
        "characterId":null,
        "enabled":"true",
        "effects":[
          {
            "id":1,
            "type":1001,
            "passiveGameItems":null,
            "passiveGameLocations":null,
            "passiveGameNPCs":null,
            "passiveGameTriggers":null,
            "passiveGameFlags":null,
            "newIntegerValue":null,
            "newBooleanValue":null,
            "newStringValue":"Metes la pila en la linterna",
            "message":null
          },{
            "id":2,
            "type":136,
            "passiveGameItems":[2],
            "passiveGameLocations":null,
            "passiveGameNPCs":null,
            "passiveGameTriggers":null,
            "passiveGameFlags":null,
            "newIntegerValue":null,
            "newBooleanValue":true,
            "newStringValue":null,
            "message":null
          },{
            "id":3,
            "type":401,
            "passiveGameItems":null,
            "passiveGameLocations":null,
            "passiveGameNPCs":null,
            "passiveGameTriggers":null,
            "passiveGameFlags":[1],
            "newIntegerValue":null,
            "newBooleanValue":true,
            "newStringValue":null,
            "message":null
          }
        ],
        "conditionalFlags":[
          {
            "id":1,
            "name":"Pila en linterna",
            "type":1,
            "booleanValue":"false",
            "integerValue":null
          }
        ],
        "activeItemConditions":null,
        "passiveItemConditions":null
      },
      {
        "id":2,
        "triggerType":1,
        "triggerSubType":0,
        "beenTriggered":"false",
        "actionId":17,
        "locationId":null,
        "itemId":1,
        "characterId":null,
        "enabled":"true",
        "effects":[
          {
            "id":1,
            "type":1001,
            "passiveGameItems":null,
            "passiveGameLocations":null,
            "passiveGameNPCs":null,
            "passiveGameTriggers":null,
            "passiveGameFlags":null,
            "newIntegerValue":null,
            "newBooleanValue":null,
            "newStringValue":"Sacas la pila de la linterna",
            "message":null
          },{/
            "id":2,
            "type":136,
            "passiveGameItems":[2],
            "passiveGameLocations":null,
            "passiveGameNPCs":null,
            "passiveGameTriggers":null,
            "passiveGameFlags":null,
            "newIntegerValue":null,
            "newBooleanValue":false,
            "newStringValue":null,
            "message":null
          },{
            "id":3,
            "type":401,
            "passiveGameItems":null,
            "passiveGameLocations":null,
            "passiveGameNPCs":null,
            "passiveGameTriggers":null,
            "passiveGameFlags":[1],
            "newIntegerValue":null,
            "newBooleanValue":false,
            "newStringValue":null,
            "message":null
          },{
            "id":4,
            "type":131,
            "passiveGameItems":[1],
            "passiveGameLocations":null,
            "passiveGameNPCs":null,
            "passiveGameTriggers":null,
            "passiveGameFlags":null,
            "newIntegerValue":0,
            "newBooleanValue":false,
            "newStringValue":null,
            "message":null
          }
        ],
        "conditionalFlags":[
          {
            "id":1,
            "name":"Pila en linterna",
            "type":1,
            "booleanValue":"true",
            "integerValue":null
          }
        ],
        "activeItemConditions":null,
        "passiveItemConditions":null
      }
    ],
    "onExamineTriggers":null,
    "visibility":1,
    "image":"item.png",
    "largeImage":null,
    "implicit":false
  },

...
]

Sí, explicar todo esto me va a llevar unas cuantas entradas. Pero no nos precipitemos y demos un repaso a los atributos que tienen en común.

  • id. Correspondería al itemId, el identificador único del objeto.
  • names. En lugar de un nombre único ahora se define una lista de nombres, de modo que el jugador pueda escribir uno de esos nombres cuando introduzca una instrucción para ser ejecutada. En el ejemplo actual el jugador podría escribir la instrucción "coger pila", "coger pila eléctrica" o "coger pila electrica" obteniendo el mismo resultado al ejecutarla.
  • description, itemListDescription. Se corresponden con itemDescription. El primer valor se muestra al examinar el objeto, el segundo valor es opcional y muestra una descripción más detallada si se está usando la opción de mostrar los objetos examinados desde el panel de objetos a pantalla completa.
  • onLocationDescription. Se corresponde a itemLocationText.
  • location. Correspondiente a itemLocation, la localización donde se encuentra el objeto. Si el valor de este atributo es 0 el objeto se encontrará en el inventario del jugador. Si es menor que 0, normalmente -1, el objeto no estará en ninguna localización ni en el inventario del jugador, no existiendo a efectos prácticos.

Como se puede ver ni el tipo de objeto ni el diálogo existen, debido a que la "inventariabilidad" -sí, me lo acabo de inventar- del objeto ahora se maneja de manera diferente y a que los personajes tienen ahora su propia definición y tratamiento.

Pero todo eso es otra historia...

Monday, February 25, 2019

Mientras tanto, en Plutón...

Sé que la última vez dije que en la siguiente entrada hablaríamos de la definición de objetos, pero he decidido cambiar ligeramente la ruta y hablar de la definición de los flags, de modo que cuando hablemos de los objetos y las acciones tengamos toda la información disponible.

Banderas de nuestros parsers

Los flags se definen en el archivo flags.json y permiten definir un valor booleano o entero que puede ser -y será- consultado desde otras partes de la aventura.

Por ejemplo, en el actual estado de La Aventura Original sólo hay un flag:

{
  "id":1,
  "name":"Pila en linterna",
  "type":1,
  "booleanValue":"false",
  "integerValue":null
}

Como habrán supuesto, es un flag que indica si el jugador ha metido o no la pila en la linterna. Esto permite desbloquear determinadas acciones, como encender y apagar la linterna o meter y sacar la pila. Al intentar realizar estas acciones el motor comprueba el valor del flag y permite o bloquea la ejecución de la acción en consecuencia.

El contenido de la clase GameFlag es bastante simple, con un identificador único para cada flag, un nombre descriptivo para facilitar su manejo por parte del diseñador/programador, un identificador de tipo (con valor 1 en este caso para identificarlo como booleano) y dos campos con el valor del flag, booleanValue para los flags booleanos e integerValue para los flags de tipo entero.

Y, ahora sí, nos vemos en la próxima entrada con la definición de objetos.

Thursday, February 21, 2019

La Navidad no se acaba hasta que lo dice el Arzobispo de Estrasburgo

Hace poco estuve en Estrasburgo visitando a unos amigos. Mi intención original era ir en diciembre para poder ver los mundialmente famosos mercados navideños de la zona, pero no pude ir porque en ese momento ellos iban a estar de viaje. Me quedé sin mercadillo pero me libré de los atentados, creo que salí ganando.

El caso es que por esa zona se toman la Navidad muy a pecho, tan a pecho que en la catedral todavía tenían puesto el Belén.


Bueno, es eso o la típica pereza que te lleva a tener los adornos puestos unos días de más pero llevada al extremo.

Monday, February 18, 2019

Mientras tanto, en Plutón...

Terminamos la comparación entre la definición de las localizaciones en las dos versiones del motor dándole un vistazo rápido al resto de cosas que se han añadido en la versión 1.0 y siguientes.

Esta ni siquiera es mi forma final

La lista se encuentra, por cierto, en plena mutación debido a que hay cosas que aun no están implementadas y otras que tienen los días contados al ser vestigios de la implementación inicial de algunas funcionalidades.

  • light. El valor de iluminación "natural" de la localización. Con el desarrollo de las fuentes de luz este atributo ha quedado obsoleto.
  • defaultImageResource. Otro atributo obsoleto de tiempos más sencillos anteriores a la llegada de las fuentes de luz.
  • exterior. Atributo lógico que indica si la localización es interior o exterior. El efecto que tendrá esto en el jugador y qué hace que una localización sea interior o exterior depende en gran medida del diseñador de la aventura. En Pluto Crash, por ejemplo, el personaje muere si entra en una localización exterior sin haber cogido antes el traje espacial.
  • onEnterTriggers. Disparadores que se activarán cuando un personaje entre en la localización. Los disparadores son una cosa bastante compleja que analizaremos más adelante. No, en serio, cuando lleguemos a ellos me agradecerán que no me halla liado con ellos ahora.
  • onExitTriggers. Disparadores que se activarán cuando un personaje salga de una habitación.
  • onGeneralActionTriggers, onActionTriggers. Pendiente de implementación. Serán los disparadores que se activarán cuando un personaje lleve a cabo una determinada acción en una localización. Hasta ahora las acciones del jugador sólo tienen disparadores asociados definidos en los objetos o los personajes sobre los que actúa, estos disparadores darán más flexibilidad a la hora de definir la respuesta del entorno a las acciones del jugador.
  • onLightChangeTriggers. Pendiente de implementación. Disparadores que reaccionarán al añadir o sustraer una fuente de luz. Como si la iluminación no fuese ya bastante complicada.
  • locationEffects. Pendiente de implementación pero con bastantes papeletas para desaparecer. Define los efectos -de los que no hablaremos ahora porque los efectos están muy relacionados con los disparadores y será mejor tratarlos a la vez- que se ejecutan mientras el personaje se encuentre en la localización. En cierto modo se solapa con los efectos disparados por los onEnterTriggers, al poderse poner en marcha efectos cuando el personaje entre en la localización que se detengan mediante un disparador definido en onExitTriggers.

Pues hasta aquí las localizaciones, permanezcan en sintonía para saber cómo ha cambiado la definición de objetos y averiguar qué fue de la definición de acciones.

Sunday, February 17, 2019

Mientras tanto, en Plutón...

Seguimos analizando las diferencias en la definición de localizaciones entre la versión primitiva del motor y la actual. Hoy toca el movimiento entre localizaciones... y la muerte.

Por donde he venido, me voy

En la versión anterior cada dirección de movimiento se definía mediante una etiqueta propia, siendo el valor dentro de la misma el identificador de la localización a la que se moverá el personaje si se mueve en esa dirección o 0 si no hay movimiento posible en esa dirección.

    <location>
        ...
        <north>4</north>
        <south>11</south>
        <east>0</east>
        <west>0</west>
        <northeast>0</northeast>
        <northwest>0</northwest>
        <southeast>19</southeast>
        <southwest>0</southwest>
        <enter>2</enter>
        <exit>0</exit>
        <up>0</up>
        <down>0</down>
    </location>


En la versión actual las direcciones de movimiento posibles se definen dentro del array exits, en el que sólo aparecerán las direcciones de movimiento que lleven a una localización.

  {
    "id":1,
    "name":"Granja",
    ...
    "exits":[
      {
        "direction":1,
        "destination":4,
        "description":""
      },
      {
        "direction":2,
        "destination":11,
        "description":""
      },
      {
        "direction":7,
        "destination":19,
        "description":""
      },
      {
        "direction":9,
        "destination":2,
        "description":""
      }
    ],
    ...
  }


Como se puede ver dentro de exits hay una serie de objetos que representan las posibles direcciones de movimiento. El atributo direction contiene el identificador correspondiente a la dirección -definido como un valor constante en las clases de configuración del motor-, el atributo destination contiene el identificador de la localización a la que se moverá el personaje y el atributo description contendrá el texto que se mostrará cuando el jugador ejecute la acción "mirar" en esa dirección cuando me ponga a implementarlo, claro. Sí, en estos artículos vamos a encontrarnos con unas cosas que no están ni siquiera a medio cocer.

En ambos casos si el valor de destination es negativo quiere decir que el personaje morirá al moverse en esa dirección -en La Aventura Original esto ocurre si el jugador se mueve dentro del volcán o en la dirección en la que se encuentran los dos barrancos. Sí, esto no es del todo justo y ese es uno de los motivos por los que quería implementar la descripción de la dirección de movimiento, para dar la opción de avisar al jugador de lo que puede pasar si se mueve en esa dirección-. Aunque esto no es del todo cierto, ya que lo que en un principio estaba pensado para las pantallas de muerte del personaje se ha acabado utilizando también para las pantallas de fin de juego. Ya hablaremos de eso cuando le toque la hora al archivo death_infos.json.

El movimiento no tiene mucho más que explicar. Cuando el jugador ejecuta una instrucción de movimiento -como "norte", "sur" o "bajar"- el parser busca el identificador asociado con esa dirección y busca dentro de exits un objeto con esa dirección en su atributo direction. Si lo encuentra se mueve al personaje a la localización especificada en destination o muestra un mensaje de error predefinido si no existe esa dirección de movimiento en la localización actual.

Mucho más sencillo que lo de las fuentes de iluminación ¿Verdad?

Wednesday, February 13, 2019

Mientras tanto, en Plutón...

Como decíamos ayer, vamos a echarle un vistazo a las diferencias entre las descripciones de las localizaciones en la versión primitiva del motor y la flamante actual versión 1.2.

Hágase la luz

La primera diferencia relevante es la definición de la descripción y la imagen de la localización, fijadas en las etiquetas description e image en el XML y en un array en la etiqueta descriptions en el JSON.
En la nueva versión se permite definir una serie de descripciones diferentes que se mostrarán en función del tipo de iluminación activa en la localización. En el ejemplo de La Aventura Orignal sólo se puede ver una descripción para cada localización, pero este ejemplo de Pluto Crash servirá mejor para ilustrar cómo funciona todo esto:

"descriptions":[
      {
        "id":1,
        "lightSource":0,
        "description":"No puedes ver nada con
la escasa luz que llega desde el pasillo.
Al moverte puedes notar cómo chapoteas en
un líquido que no debería estar aquí.",
        "locationImage":"loc_010.png"
      },
      {
        "id":2,
        "lightSource":2,
        "description":"Las paredes del pozo de
mantenimiento están cubiertas de tuberías y gruesos
cables. El suelo está cubierto de fluido hidráulico.",
        "locationImage":"loc_010_02.png"
      }
    ]


En este caso se proporcionan dos descripciones diferentes, una para dos valores de iluminación diferentes. En el primer elemento del array se pueden ver los valores que se usarán cuando el usuario se encuentre en la localización sin una fuente de luz propia -valor lightSource 0, correspondiente a oscuridad total- o con una fuente de luz artificial -valor lightSource 2-.

En el ejemplo de La Aventura Original podemos ver que ambas localizaciones tienen una fuente de luz con un lightValue de 6, que corresponde a la luz natural diurna y normalmente tiene preferencia sobre el resto de fuentes de iluminación.

"lightSources":[
      {
        "id":1,
        "sourceId":-1,
        "lightValue":6
      }
    ]


Normalmente las localizaciones sólo cuentan con una fuente de luz, la de la propia localización. Sin embargo, es posible que el personaje o un efecto dejen en una localización un objeto que emita luz; en este caso se añadirá a las fuentes de luz de la localización una fuente de luz adicional correspondiente al objeto. En este caso la nueva fuente de luz tendrá un valor de -2 en el atributo sourceId, frente al valor -1 correspondiente a las fuentes de luz propias de la localización.

Así, al entrar en una localización el motor compara las fuentes de luz presentes en la localización -las fuentes de luz definidas en la propia localización y la mejor fuente de luz emitida por los objetos del inventario del personaje-, quedándose con la que tenga el valor lightValue más alto y usará la descripción con un valor lightSource igual, o la descripción con el valor lightSource más alto si todos son menores que la fuente de luz actual.

La cosa se ha hecho un poco larga así que seguiremos con el resto de diferencias en la próxima entrada. Ale, a programar.

Monday, January 28, 2019

Mientras tanto, en Plutón...

Terminadas las mejoras que había mencionado en la última entrada y solucionados algunos errores pendientes he empezado ya a trabajar en La Aventura Original propiamente dicha. Para ello he desenterrado el código de la versión actualmente publicada y, Enki, muchacho, hay que ver lo que has crecido.

Cómo hemos cambiado.

Los elementos fundamentales de la aventura estaban ya entonces definidos en documentos fuera del código, pero de aquella me había decantado por el XML frente a los actuales JSON. Teniendo en cuenta que el código es Java la opción actual es la más lógica, pero de aquella conocía mejor el XML y, a fin de cuentas, tampoco hay tanta diferencia.

En lo que sí hay una diferencia abismal es en el número y contenido de los archivos. Mientras en la versión 0 se usaban estos tres:

game_actions.xml
game_items.xml
game_locations.xml

En la versión actual se usa el triple:

adventure.json
conversation_trees.json
counters.json
death_infos.json
flags.json
game_items.json
locations.json
npcs.json
triggers.json

El cambio más evidente -aparte del número- es la desaparición el archivo que define las acciones del jugador, que ahora han pasado a estar definidas dentro de los objetos y siguen un proceso lógico completamente diferente, más flexible, completo y, claro, complejo.

No me voy a extender mucho más, sólo les dejaré aquí un ejemplo para comparar cómo se definían las localizaciones en la versión inicial y en la actual de modo que puedan ver cómo de grave es la locura que me ha dado con este proyecto.

game_locations.xml

<locations>
    <location>
        <id>1</id>
        <name>GRANJA</name>
        <description>Estás en una granja abandonada. Muy cerca puedes ver una casa de ladrillo abandonada rodeada de árboles. Hacia el sur fluye un rio.</description>
        <image>1</image>
        <north>4</north>
        <south>11</south>
        <east>0</east>
        <west>0</west>
        <northeast>0</northeast>
        <northwest>0</northwest>
        <southeast>19</southeast>
        <southwest>0</southwest>
        <enter>2</enter>
        <exit>0</exit>
        <up>0</up>
        <down>0</down>
    </location>
    <location>
        <id>2</id>
        <name>GRANJA: INTERIOR</name>
        <description>Estás en una habitación bastante guarra de paredes desconchadas y suelo roto. Ves la oscura boca de un pozo por la que baja una mohosa escalera.</description>
        <image>2</image>
        <north>0</north>
        <south>0</south>
        <east>0</east>
        <west>0</west>
        <northeast>0</northeast>
        <northwest>0</northwest>
        <southeast>0</southeast>
        <southwest>0</southwest>
        <enter>0</enter>
        <exit>1</exit>
        <up>0</up>
        <down>3</down>
    </location>
...

</locations>

locations.json

[
  {
    "id":1,
    "name":"Granja",
    "descriptions":[
      {
        "id":1,
        "lightSource":6,
        "description":"Estás en una granja abandonada. Muy cerca puedes ver una casa de ladrillo abandonada rodeada de árboles. Hacia el sur fluye un rio.",
        "locationImage":"loc_01.png"
      }
    ],
    "light":6,
    "defaultImageResource":"room_01.png",
    "lightSources":[
      {
        "id":1,
        "sourceId":-1,
        "lightValue":6
      }
    ],
    "exterior":true,
    "exits":[
      {
        "direction":1,
        "destination":4,
        "description":""
      },
      {
        "direction":2,
        "destination":11,
        "description":""
      },
      {
        "direction":7,
        "destination":19,
        "description":""
      },
      {
        "direction":9,
        "destination":2,
        "description":""
      }
    ],
    "onEnterTriggers":null,
    "onExitTriggers":null,
    "onGeneralActionTriggers":null,
    "onActionTriggers":null,
    "onLightChangeTriggers":null,
    "locationEffects":null
  },
  {
    "id":2,
    "name":"Granja: Interior",
    "descriptions":[
      {
        "id":2,
        "lightSource":6,
        "description":"Estás en una habitación bastante guarra de paredes desconchadas y suelo roto. Ves la oscura boca de un pozo por la que baja una mohosa escalera.",
        "locationImage":"room_02.png"
      }
    ],
    "light":6,
    "defaultImageResource":"room_02.png",
    "lightSources":[
      {
        "id":1,
        "sourceId":-1,
        "lightValue":6
      }
    ],
    "exterior":false,
    "exits":[
      {
        "direction":10,
        "destination":1,
        "description":""
      },
      {
        "direction":12,
        "destination":3,
        "description":""
      }
    ],
    "onEnterTriggers":null,
    "onExitTriggers":null,
    "onGeneralActionTriggers":null,
    "onActionTriggers":null,
    "onLightChangeTriggers":null,
    "locationEffects":null
  },
...
]



Menudo cambio ¿Eh? En la próxima entrada explicaré qué hace cada cosa... y qué no hace, claro.
Ale, a explorar.

Wednesday, January 23, 2019

Mientras tanto, en Plutón...

Por petición popular -en serio- he empezado a trabajar en la segunda parte del remake de La Aventura Original. A lo largo de todo el proceso escribiré aquí comentarios que considere interesantes acerca del desarrollo del juego y de las mejoras que será necesario realizar en Enki, el motor de juego que también usé para Pluto Crash.

Si una pila está dentro de una linterna ¿Está en tu inventario?

Aun siendo un motor bastante competente, Enki tiene algunas limitaciones que hasta ahora había ignorado por el bien de mi salud mental y de la planificación temporal de Pluto Crash. Una de estas limitaciones tiene mucho que ver con uno de los puzzles de La Aventura Original.

Como ya sabrá cualquiera que haya jugado a La Aventura Original original -vaya lío- la pila de tu linterna tiene los días contados y necesitarás recargarla si no quieres partirte la crisma mientras te paseas por la Gran Caverna. Para ello debes encontrar la máquina cargadora de pilas y recargar la pila de tu linterna, para lo que previamente debes sacar la pila de la linterna.

Pues bien, tal y como está programado Enki esto no es posible. Al meter la pila en la linterna la misma desaparece del inventario del jugador y es enviada a una especie de limbo de modo que el jugador no puede interactuar con ella, con lo que no podría sacarla de la linterna porque al parsear la instrucción el intérprete vería que el objeto "pila" ya no existe.

Para solucionar esto me he sacado de la manga lo que llamo "objetos implícitos". Éstos objetos están en el inventario del jugador pero no aparecen en él y no pueden ser el objeto activo de las acciones normales del jugador, sólo de aquellas acciones que estén específicamente indicadas en la definición del objeto.
Así, tras meter la pila en la linterna el jugador podrá sacarla de la misma pero, por ejemplo, no podrá examinarla o dejarla en el suelo sin sacarla antes.

Esto abre la puerta también a una mejor gestión de lo que se podrían considerar objetos equipables. En Pluto Crash, por ejemplo, al coger el traje espacial en la sala de descompresión se indica al personaje que se lo ha puesto y a partir de este momento se considera que lo lleva siempre de modo que puede moverse por el exterior de la base.
Internamente lo que realmente ha ocurrido es que el objeto "Traje espacial" ha sido enviado al limbo de objetos que no están en ninguna parte y se ha activado un flag que indica que el personaje lo lleva puesto, lo que es perfectamente válido para las necesidades del juego pero impide que el jugador pueda quitarse el traje.

¿Elfito? ¿Eres tú, Elfito?


Debido a las limitaciones que tuve que imponerme al desarrollar el remake de La Aventura Original, el personaje conocido como Elfito tuvo que desaparecer. El motor del juego había sido desarrollado ad hoc para lo que a fin de cuentas era el trabajo para un módulo del máster que estaba haciendo en aquel momento y simplemente no era factible desarrollar un sistema que te permitiese conversar con él, por no hablar de que se supone que el cabroncete es medio invisible y había que controlar si la linterna estaba encendida o apagada para poder interactuar con él.

Todo eso ha sido resuelto en el motor actual, pero como no quería modificar el guion de mi remake he decidido que Elfito aparecerá pero será dentro de la Gran Caverna, en un giro argumental que espero sea del gusto de todos los fans del original.

Sunday, January 13, 2019

Las Pifias Vandálicas de Le Pédant

Teniendo en el blog una sección dedicada a pintadas magistrales o jocosas -tanto intencionada o accidentalmente- y ocasionalmente evolutivas he acabado desarrollando la costumbre de fijarme en cualquier garabato callejero con el que me cruzo por irrelevante que pueda parecer. No todos pasan la criba necesaria para ser una joya oficial, pero sería una pena dejar que las mejores imbecilidades pasen al olvido.

Así, y sin más preámbulos, les presento la primera Pifia Vandálica de Le Pédant.

+EDUCACION
-SANGRE

Éste es el típico caso en el que las prisas y la necesidad de llevar la concisión más allá de la propia capacidad cerebral acaban provocando el equivalente en el mundo real de una compresión con pérdida, porque ¿Qué quería decir el pifioso de hoy?
 
¿Acaso cree que se siguen equilibrando los humores a base de sanguijuelas?
¿Tiene algo en contra de las transfusiones de sangre?
¿Ha visto demasiadas veces Blade con la consiguiente pérdida de contacto con la realidad?
Y aun diría más ¿Cree que se puede operar una clínica sólo con el graduado escolar y una elegante bata blanca?

Supongo que nunca lo sabremos.

Saturday, January 05, 2019

Temporada de Yule en Le Pédant

Venga, que si rascan un poco seguro que aun encuentran un poco de espíritu navideño.

Friday, December 28, 2018

Temporada de Yule en Le Pédant

Comentaba el año pasado que estaba Ponferrada poco navideña y parece que este año la ciudad quiere batir el récord. Por suerte en el centro comercial siempre montan un belén bastante majo que ayuda a aliviar la ranciedad que se apodera de la ciudad.





Sí, tiene arena de gladiadores y todo, aquí siempre ha habido mucha afición por partirle el cráneo al prójimo.

Wednesday, December 19, 2018

Pluto Crash

¿Acaso pensaban que en Navidad iba a dejar de darles la matraca con el juego?¡JUAS!¡Pero qué panolis!


Monday, December 17, 2018

Temporada de Yule en Le Pédant

Ah, Inglaterra, país de Navidades a tope, comida horrible, postres que compensan lo hórrido de la mencionada comida y bebercio desaforado. Hoy les traemos sólo tres de estas cosas porque, evidentemente, nadie quiere aprender a hacer un puto pastel de riñones.







Venga, a beber. Y a cocinar, claro.

Sunday, December 16, 2018

Temporada de Yule en Le Pédant

Vayan engrasando la zambomba, abrillantando las bolas y... esto ha sonado mucho peor de lo que esperaba.

Bueno, sea como sea ha llegado la segunda mejor temporada del año en Le Pédant y vamos a empezar, por supuesto, con un Belén.



En este caso es un cacho diorama que se han montado en Cristalería Serna, en el Camino de las Aguas (Salamanca).

Monday, December 10, 2018

Pluto Crash

¿He tardado sólo cinco días en adaptar y publicar la nueva versión? Pero qué me está pasando, como siga así voy a acabar madrugando o, peor aun... ¡Madrugando porque me he buscado un trabajo!

¿Llevas una linterna, una palanca, una llave inglesa,
unos alicates y un destornillador en el bolsillo
o es que te alegras de verme?

Qué sofoco, madre mía madre mía. Bueno, a lo que iba, que ya está en Google Play la versión 1.1 de Pluto Crash adaptada para tablets y otros cachirulos con pantallas tamaño familiar.

Venga, rapidito que se agotan.

Wednesday, December 05, 2018

Mientras tanto, en Plutón...

Qué ¿Disfrutando de Pluto Crash?
¡Juas! Pues claro que no, qué cosas tengo a veces.

Bueno, sea como sea lo prometido es deuda y la adaptación para tablets va a buen ritmo. En una clásica maniobra lepedántica empecé las soluciones por el camino más complicado posible con una pila de líneas de código que no funcionaban pero por suerte soy más listo de lo que pensaba y la cosa ya está terminada.

Un mockup no es un tipo de café, so gañanes.

Terminada en el motor básico, claro. Para el juego en sí voy a tener que cambiar bastantes cosas y aun queda el problema de la maquetación de algunas pantallas especiales, aunque teniendo en cuenta lo que tardé en ponerme las pilas para terminar el juego ahora mismo voy como una centella.

Friday, November 30, 2018

Pluto Crash

Dicen que lo bueno se hace esperar y, que si es breve, es dos veces bueno. Claro que también dicen que quien madruga no mueve molino y eso no tiene ningún sentido así que no me fiaría yo demasiado de la sabiduría popular.

Sea como sea ya está disponible mi nuevo juego, Pluto Crash, tras un montón de retrasos total, absoluta y completamente justificados.

https://play.google.com/store/apps/details?id=com.whg.plutocrash

Y si no les gustan las aventuras conversacionales seguro que conocen a algún tarado mental al que sí, a simple vista parecemos gente normal, así que comenten el evento con sus familiares, vecinos, compañeros de trabajo y desconocidos en el autobús, nunca se sabe.

Por cierto, si lo van a jugar en una tablet... bueno, por poder se puede pero no se va a ver muy bien, así que si tienen mucha prisa por jugarlo adelante pero siempre pueden esperar a que saque la versión optimizada. No tardaré demasiado. Creo.

Monday, November 26, 2018

Sigan caminando, aquí no hay nada que ver




Igual debería ir pensando en hacer algo con mi vida. O no, esto es más divertido a fin de cuentas.