Wednesday, March 27, 2019

Mientras tanto, en Plutón...

En esta entrada vamos a examinar los cuatro atributos de la clase GameItem que nos quedan pendientes. Se trata de una cosa un tanto compleja, intentaré explicarme lo mejor posible.

Antes de proceder con la explicación vamos a echarle un vistazo al contenido del archivo game_actions.xml de la versión 0. En este archivo se definían las acciones que podía llevar a cabo el jugador, o más exactamente las acciones que tenían un resultado específico más allá de las acciones con resultados predeterminados, como "mirar", "examinar" o las órdenes de movimiento.

Usaremos como ejemplo la acción "USAR PILA LINTERNA", definida en el XML como sigue:

<actions>
   <action>
      <actionId>17</actionId>
      <actionObject_1>-1</actionObject_1>
      <actionSubstituteObject_1>0</actionSubstituteObject_1>
      <actionObject_2>-2</actionObject_2>
      <actionSubstituteObject_2>-3</actionSubstituteObject_2>
      <locationEffect>0</locationEffect>
      <locationSubstitute>0</locationSubstitute>
      <actionMessage>Insertas la pila en el interior de la linterna.</actionMessage>
   </action>

...
</actions>

El identificador de la acción se especificaba en actionId, en este caso el valor 17 corresponde a la acción "usar".

El objeto activo -en este caso la pila, cuyo identificador es 1 tal y como ya vimos en el archivo game_items.xml- se definía en actionObject.
Si este valor era negativo uno de los resultados de la acción es la eliminación del objeto y su sustitución por el objeto indicado en actionSubstituteObject_1. En nuestro ejemplo actionSubstituteObject_1 tiene valor 0, por lo que el objeto activo no se sustituía por ningún otro objeto.

El objeto pasivo, en este caso la linterna, se definía en actionObject_2. Al igual que en el caso de los objetos activos, si el valor de actionObject_2 era negativo debía eliminarse el objeto y sustituirse por el indicado en actionSubstituteObject_2.

Los valores de locationEffect y locationSubstitute funcionaban de manera similar, indicando una localización que se vería afectada por la acción y el identificador de la localización que debía tomar su lugar.

Finalmente, actionMessage definía el mensaje que se debía mostrar al llevar a cabo la acción.

Bastante apañado, pero evidentemente muy limitado. Cada acción podía tener efecto sólo sobre dos objetos que debían además ser parte de la acción y este efecto se limitaba a la creación o eliminación de objetos. Supongo que no extrañará a nadie que las acciones fuesen de lo primero a lo que hinqué el diente cuando empecé la versión actual y que apenas haya sobrevivido nada.

Trigger warning

Al abordar la nueva versión decidí pasar la definición de las acciones a los objetos que toman parte en ellas. Estas acciones se definen en una serie de atributos dentro de la clase GameItem:
  • onPickTriggers 
  • onDropTriggers 
  • onExamineTriggers 
  • onActionTriggers
Estos cuatro arrays contienen los disparadores que se activarán al realizar determinadas acciones con el objeto en el que se definen: al añadir el objeto al inventario, al dejar el objeto en una localización desde el inventario, al examinar el objeto y al realizar una acción que no se incluya en los tres casos anteriores.

Como vimos en el primer artículo sobre los objetos la definición de la acción "METER PILA LINTERNA" tiene este aspecto:

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


Los diferentes atributos que podemos ver son:
  • id. Identificador numérico del disparador.
  • triggerType. Tipo de disparador. Define el tipo de acción que se debe llevar a cabo para activar el disparador.
  • triggerSubtype. Tipo secundario. Actualmente un disparador puede tener un tipo secundario "normal", "one-shot" o "execute and break", estando reservado el segundo tipo para aquellos disparadores que sólo puedan ser activados una vez y el tercero para los disparadores que deban impedir que el resto de disparadores definidos en el objeto se comprueben si el mismo ha sido activado.
  • beenTriggered. Valor booleano que indica si un disparador ha sido activado. Sólo es relevante al usarse con el tipo secundario "one-shot".
  • actionId. El identificador numérico de la acción que activa el disparador.
  • locationId. No implementado. En un futuro servirá para limitar las localizaciones en las que se puede llevar a cabo una acción.
  • itemId. Identificador numérico del objeto pasivo de la acción, si existe.
  • characterId. Identificador numérico del personaje que actúa como objeto pasivo de la acción, si existe.
  • enabled. Valor booleano que indica si un disparador puede o no activarse.
  • conditionalFlags. Array de objetos GameFlag. Los flags definidos en flags.json deben tener el mismo valor que el definido en el disparador para que éste pueda activarse. Pueden ver una de las entradas anteriores para saber más sobre los flags.
  • activeItemConditions. Un array de objetos tipo GameFlag que definen requisitos específicos que debe cumplir el objeto activo.
  • passiveItemConditions. Un array de objetos tipo GameFlag que definen requisitos específicos que debe cumplir el objeto pasivo.
  • effects. Un array que contiene los efectos que se ejecutarán si el disparador es activado con éxito.
Al recibir la orden para ejecutar la instrucción "METER PILA LINTERNA" el intérprete divide la misma en partes, detectando la acción "METER", el objeto activo "PILA" y el objeto pasivo "LINTERNA" y procede con los siguientes pasos:
  1. El motor comprueba que el objeto activo tiene definidos disparadores en el atributo onActionTriggers y busca aquellos cuyo triggerType corresponda al tipo de acción que se quiere realizar -en este caso, el tipo genérico "acción"-, que el atributo id del objeto pasivo se corresponde con el atributo itemId del disparador y que el atributo actionId del disparador se corresponde con el identificador de la acción interpretada -en este caso "meter", cuyo identificador es 16-.
  2. El atributo enabled tiene valor true.
  3. Si el disparador es de tipo "one-shot" el atributo beenTriggered tiene valor false.
  4. El valor de los flags definidos en el disparador corresponde al valor de los mismos en flags.json.
  5. Los flags definidos en activeItemConditions se corresponden con los atributos del objeto activo.
  6.  Los flags definidos en pasiveItemConditions se corresponden con los atributos del objeto pasivo.
Si se cumplen estas condiciones, el motor pasará a ejecutar los efectos definidos en el disparador. Es en estos efectos, definidos en el array effects, donde reside toda la potencia. Los efectos se definen mediante la clase GameEffect, cuyos atributos son:
  • id. Identificador numérico del efecto.
  • type. Tipo de efecto, este valor determinará el resultado del mismo.
  • passiveGameItems. Objetos que se verán afectados por el efecto.
  • passiveGameLocations. Localizaciones que se verán afectadas por el efecto.
  • passiveGameNPCs. Personajes que se verán afectados por el efecto.
  • passiveGameTriggers. Disparadores que se verán afectados por el efecto.
  • passiveGameFlags. Flags que se verán afectados por el efecto.
  • newIntegerValue. Nuevo valor entero que tomará el atributo modificado por el efecto.
  • newBooleanValue. Nuevo valor booleano que tomará el atributo modificado por el efecto.
  • newStringValue. Nueva cadena de texto que se asignará al atributo modificado por el efecto.
  • message. Mensaje que se mostrará al ejecutar el efecto.
En nuestro ejemplo, los efectos a ejecutar tienen los identificadores 1001 -mostrar el mensaje definido en newStringValue por la salida de texto-, 136 -asigna al atributo implicit del objeto pasivo el valor indicado en newBooleanValue- y 401, que asigna a los flags especificados en el array passiveGameFlags el valor de newBooleanValue. Así, al meter la pila en la linterna se mostrará al jugador el mensaje "Metes la pila en la linterna", la pila pasará a ser un objeto implícito y el flag con identificador 1 que indica si la pila está en la linterna o no pasará a tener valor true.

¿Se han enterado de algo? Si es así, enhorabuena, porque yo mismo he tenido que darle un buen repaso al código para acordarme de todo.

No comments: