Notxor tiene un blog

Defenestrando la vida

Programando un módulo para Minetest

2021-05-28

Hace poco hablé de Minetest y comenté que lo estaba valorando como una posible herramienta educativa... todo consiste en programar lo que necesitas para poder mostrarlo en un entorno ya pensado para poder interactuar con él. Me preguntaba entonces, en aquel artículo, si sería muy difícil programar tus propios módulos para adaptar la plataforma a tus necesidades. Bien, he comenzado la exploración y tras algunos tropiezos voy encontrando la manera de hacerlo. En este artículo vengo a contar mis primeros pasitos. ¿Qué hace el módulo de prueba que quiero desarrollar? Un espejo mágico. Ya sabéis mi debilidad con las conversacionales y yo quiero un objeto o un algo con el que conversar: una estupidez artificial es lo más adecuado.

Montar un mundo

Lo primero que hice fue un mundo de pruebas, sólo para ver si era complicado y trasteé un poco con los módulos, cuáles se cargan, cuáles no... usé módulos hechos sólo por probar1 y el mundo quedó resultón.

Captura-pantalla_empezar-juego.png

En la pantalla se puede ver que podemos crear un mundo simplemente pulsando el botón donde dice Nuevo. Eso hará que nos aparezca la pantalla de crear un nuevo mundo.

Otro aspecto importante es que si habilitamos las opción Hospedar servidor desde cualquier ordenador conectado a la misma red, se puede compartir el mundo y jugar juntos2 desde los equipos conectados a la misma.

Echado un primer vistazo al juego en general, ya vemos que podemos utilizarlo en modo monousuario, como si fuera un lego virtual; poner un servidor en una red local y jugar con los usuarios de dicha red, algunas pruebas he hecho con mi hija conectados ambos en el mismo mundo local3 y por último, también podemos conectarnos a un servidor de los muchos que hay por Internet y jugar a un mundo que ha hecho otro4.

Captura-pantalla_crear-juego.png

El diálogo anterior nos permite generar un mundo con las opciones que tenemos ahí. Ponerle un nombre, modificar la semilla aleatoria sobre la que se generará el mapa, etc. Como lo que necesito es un mundo para hacer pruebas tampoco hace falta mucha historia, pero ya puestos hice un mundo con todo (cavernas, valles, y todas las opciones posibles). Lo llamé Kvintero por otro proyecto con el que ando, pero tampoco es importante a estas alturas el nombre. El caso es que funciona y lo puedo recorrer, crear objetos y disponerlos en él como me parezca.

Es decir, ahora tenemos un terreno muy bonito, pero sigue siendo un mundo por construir. Hay montañas, valles, cuevas, ríos... todo en automático y sin esfuerzo. Como inicio, ya es bastante prometedor. Puedes moverte e interactuar, pero si quieres tener cosas más interesantes es aconsejable cargar algunos módulos, como he dicho.

Captura-pantalla_cargar-modulos.png

En esa lista, al seleccionar el nombre de uno de los módulos nos mostrará la información del módulo:

Captura-pantalla_activar-modulo.png

Como podemos observar, nos permite activarlo con el selector de arriba, ver las dependencias (incluso las opcionales), etc. Además, otra de las funciones recomendables de este diálogo lo proporciona el botón que hay en la parte inferior derecha: Encontrar más mods.

Captura-pantalla_repositorio-modulos.png

Podemos obtener información más detallada de los módulos pulsando en su botón de opción (el de más a la derecha) o incorporarlo al mundo pulsando sobre el botón +. Si lo hacemos, el programa se conecta con el repositorio de módulos y descarga el código en tu máquina y lo guarda en el directorio ~/.minetest/mods/

La estructura del directorio .minetest es buena tenerla en cuenta... En ese lugar también guarda el sistema información que nos viene muy bien, un fichero llamado debug.txt donde nos mostrará avisos que nos pueden servir para depurar nuestro código.

También he descubierto, que lanzando Minetest desde la consola, nos aparece información adicional que nos va declarando todas las acciones que se llevan a cabo en el mundo. He utilizado el siguiente comando:

minetest > servidor.log 2> acciones.log

Después podemos observar el contenido de dichos archivos. Por ejemplo, servidor.log puede contener algo así:

Loaded texture: /usr/share/minetest/games/minetest_game/menu/header.png
Loaded texture: /home/notxor/.minetest/games/Lord-of-the-Test-1.1.0/menu/icon.png
Loaded texture: /usr/share/minetest/games/devtest/menu/icon.png
Loaded texture: /usr/share/minetest/games/minetest_game/menu/icon.png
Loaded texture: /usr/share/minetest/textures/base/pack/plus.png
(T@mobs_animal)[MOD] Mobs Redo Animals loadedE

El fichero de acciones podría ser algo así:

2021-05-28 19:08:21: [Main]: Automatically selecting world at [/home/notxor/.minetest/worlds/Kvintero]
[ALSOFT] (EE) Failed to set real-time priority for thread: Operation not permitted (1)
[ALSOFT] (EE) Failed to set real-time priority for thread: Operation not permitted (1)
2021-05-28 19:08:29: ACTION[Main]: [MOD] Mobs Redo loaded
        .__               __                   __   
  _____ |__| ____   _____/  |_  ____   _______/  |_ 
 /     \|  |/    \_/ __ \   __\/ __ \ /  ___/\   __\
|  Y Y  \  |   |  \  ___/|  | \  ___/ \___ \  |  |  
|__|_|  /__|___|  /\___  >__|  \___  >____  > |__|  
      \/        \/     \/          \/     \/        
2021-05-28 19:08:29: ACTION[Main]: World at [/home/notxor/.minetest/worlds/Kvintero]
2021-05-28 19:08:29: ACTION[Main]: Server for gameid="minetest" listening on 0.0.0.0:30000.
2021-05-28 19:08:32: ACTION[Server]: Notxor [127.0.0.1] joins game. List of players: Notxor
2021-05-28 19:08:32: WARNING[Main]: Irrlicht: Could not open file of texture: character.png
2021-05-28 19:08:34: ACTION[Server]: Notxor uses espejito:node, pointing at [node under=61,12,320 above=61,13,320]
2021-05-28 19:08:37: ACTION[Server]: Notxor pregunta al espejito: Hola
2021-05-28 19:08:41: ACTION[Server]: Notxor uses espejito:node, pointing at [node under=61,12,320 above=61,13,320]
2021-05-28 19:08:44: ACTION[Server]: Notxor pregunta al espejito: asdfasd 
2021-05-28 19:08:49: ACTION[Server]: Notxor uses espejito:node, pointing at [node under=61,12,320 above=61,13,320]
2021-05-28 19:08:58: ACTION[Server]: Notxor pregunta al espejito: Contenido de la pregunta
2021-05-28 19:09:01: WARNING[Main]: Irrlicht: Could not open file of texture: mobs_chicken_white.png
2021-05-28 19:09:06: ACTION[Server]: Notxor uses espejito:node, pointing at [node under=61,12,321 above=61,13,321]
2021-05-28 19:09:09: ACTION[Server]: Notxor pregunta al espejito: asdf asd
2021-05-28 19:09:14: ACTION[Server]: Notxor digs default:dirt_with_grass at (62,12,321)
2021-05-28 19:09:16: ACTION[Server]: Notxor digs default:dirt_with_grass at (62,12,322)
2021-05-28 19:09:17: ACTION[Server]: Notxor places node default:dirt_with_grass at (62,12,322)
2021-05-28 19:09:18: ACTION[Server]: Notxor places node default:dirt_with_grass at (62,12,321)
2021-05-28 19:09:19: ACTION[Server]: Notxor digs default:dirt at (63,12,320)
2021-05-28 19:09:19: ACTION[Server]: Notxor digs default:dirt_with_grass at (62,12,320)
2021-05-28 19:09:20: ACTION[Server]: Notxor places node default:dirt_with_grass at (62,12,320)
2021-05-28 19:09:21: ACTION[Server]: Notxor places node default:dirt_with_grass at (63,12,320)
2021-05-28 19:09:32: ACTION[Main]: Server: Shutting down

Se puede observar, que he realizado algunas acciones sólo para ver cómo se muestran en la salida. El redireccionar las salidas a ficheros lo he hecho sólo a efectos de tener algo que cortar y pegar en el artículo, pero no me parece una mala política si quieres tener un seguimiento de lo que hacen los usuarios en tu servidor.

Programar un módulo

Esto es sólo una prueba, pero no me ha costado demasiado hacerlo, más allá de algún pequeño atasco por falta de información, no porque haya poca, sino porque me he tirado al ruedo sin leer el manual de instrucciones y me ha pillado todo el asunto muy poco leído e instruido.

El caso es que estuve pensando durante algún tiempo qué tipo de módulo podía escribir, algo que fuera relativamente fácil de modelar, que fuera, como mucho, un cubo para mostrarse en un voxel... pero sí tenía claro que quería programar un diálogo de entrada de datos y también una salida que fuera de texto a la consola. Todas esas condiciones se cumplían en el intento de hacer un espejo mágico al que hacer preguntas y que conteste.

Preparar el proyecto, fue muy sencillo: creé un directorio llamado espejito dentro del directorio de módulos, es decir:

~./.minetest/mods/espejito

El único contenido que puse en el primer paso fue el fichero mod.conf con el siguiente contenido:

name = espejito
description = Proporciona un espejo mágico con el que hablar
depends = default
author = Notxor

Como se puede ver es una configuración muy sencilla: proporciona un nombre, una descripción, las dependencias de otros módulos y el autor. Es decir, la información mínima para funcionar, incluso algunos de los campos no son necesarios para que Minetest lo procese como un módulo.

También creé un fichero llamado init.lua, al principio vacío.

Crear el objeto

Lo primero, y quizá lo más fácil, fue introducir un objeto nuevo en el módulo. Sólo hay que registrar el nodo:

  minetest.register_node(
        "espejito:node", {
           description = "Espejito mágico",
           -- drawtype = "glasslike",
           drawtype = "signlike",
           paramtype2 = "wallmounted",
           selection_box = {
              type = "wallmounted",
           },
           tiles = {"espejito_textura.png"},
           on_use = function(itemstack, player, pointed_thing)
              minetest.show_formspec(player:get_player_name(), "pregunta_espejo", get_pregunta_formspec())
           end,
           sounds = default.node_sound_glass_defaults(),
           groups = {cracky = 3},
})

Este es el código tal y como está funcionando ahora, ha habido varias evoluciones según miraba la documentación. De primeras, por ejemplo, lo definí como un objeto drawtype de cristal, sin embargo, después de mirar la documentación me pareció más adecuado que se dibujara como un cartel o señal, además seleccioné el tipo de selección como montado en la pared que es donde debería colgarse un espejo. También, proporcioné una textura que se encuentra en el siguiente path:

~/.minetest/mods/espejito/textures/espejito_textura.png

Es un PNG de \(16 \times 16\) creado para la ocasión y, como se puede apreciar, no hace falta especificar el path, porque Minetest entiende que debe estar en el lugar estándar.

Podemos ver también, que se especifican los sonidos de cristal. Es decir, cuando lo rompemos, lo golpeamos o realizamos una acción con él se utilizarán los sonidos que hay para cristal en el módulo default.

También llamará la atención la aparición de on_use donde se establece una función que se ejecutará cuando queramos usarlo, sin embargo, lo explicaré más detenidamente más adelante, porque es donde está el meollo del código. Pero antes de meternos en código quiero comentar algunos aspectos interesantes de la declaración de los objetos, por ejemplo el de cómo crear un espejito mágico:

minetest.register_craft(
   {
      type = "shaped",
      output = "espejito:node",
      recipe = { {"default:glass", "default:diamond", "default:glass"} },
})

El formato de la creación de un espejito es sencillo de entender: es un tipo forma, se genera una salida (output = "espejito:node") con una receta (recipe)5.

Captura-pantalla_espejo-en-pantalla.png

Simplemente con esto ya aparece el objeto en el mundo. Aunque no se puede hacer mucho con él: podemos crearlo, destruirlo, pero poco más.

Dotar al objeto de acciones

Como dije antes, al declarar el objeto se ha metido una función que responderá a la acción on_use. ¿Qué hace esa función? Mostrar un formulario, veamos el siguiente código:

local function get_pregunta_formspec()
   return "size[5.75,3.5]"..
      "label[1,0.25;¿Qué desea vuestra excelencia?]"..
      "field[0.5,1.5;5.25,1;pregunta;Pregunta: ;]"..
      "button_exit[1.5,2.3;3,0.8;preguntar;Preguntar]"
end

local function conversacion()
   local frases = {
      "Cuñaaaaao...",
      "No puedorrr, no puedorrr...",
      "¡¿Que si quiere bolsa?!"
   }
   return frases[math.random(#frases)]
end

como vimos en on_use se llamaba a show_formspec, se obtiene el nombre del jugador, se pone nombre al formulario (pregunta_espejo) y se llama a la función get_pregunta_fromspec().

Captura-pantalla_formulario.png

Para capturar el contenido del formulario:

minetest.register_on_player_receive_fields(function(player, formname, fields)
      local nombre_jugador = player:get_player_name()
      if fields.pregunta ~= nil then
         -- información de depuración a la consola
         minetest.log("action", nombre_jugador .. " pregunta al espejito: " .. fields.pregunta)
         if (formname == "pregunta_espejo" and fields.pregunta ~= "") then
            minetest.chat_send_player(nombre_jugador, conversacion())
         end
      end
end)
Captura-pantalla_respuestas.png

Como se puede observar, independientemente de lo que le preguntemos al espejo, lo que hace la función conversacion es enviar al azar una frase de las que hay en su lista de frases. Vale, que no son muchas ni muy inteligentes... pero es una estupidez artificial, que es como una inteligencia artificial pero sin intentar engañar al usuario.

Conclusiones

Es el primer módulo para Minecraft que hago y aún no está terminado. Mi intención es hacer un bot conversacional.

Los siguientes pasos serán mejorar la funcionalidad de conversacion, primero haciendo una especie de Eliza o algo similar. El objetivo no es crear algo usable para el juego como tal, aunque si es así, tampoco pasaría nada. El objetivo último es aprender cómo funciona la creación de módulos y profundizar hasta tener una idea más precisa de lo que es factible a la hora de diseñar un juego para esta plataforma.

Para más información podéis consultar un libro introductorio sobre la programación de módulos para Minetest y la API de la plataforma en los siguientes enlaces:

Footnotes:

1

Y por tener código que mirar, copias y plagiar si fuera necesario. Algo que más que necesario es imprescindible cuando no sabes muy bien qué estás haciendo, como es el caso.

2

Recuerda abrir en el cortafuegos el puerto 30000, o el que utilices para el juego.

3

Es una experiencia bastante más agradable, no hay lag y todos los jugadores son amigables (depende de con quién te juntes, claro)

4

Si hace poco que te has interesado por este mundillo, seguramente el mundo que diseñe otro para jugar será mucho mejor del que puedes crear tú en el primer momento... como me sucede a mí.

5

La estructura de la receta es conveniente mirarla en la documentación. No es complicado de entender, pero necesita una explicación larga, especialmente si no has jugado nunca a este juego.

Categoría: minetest juegos

Comentarios

Debido a algunos ataques mailintencionados a través de la herramienta de comentarios, he decidido no proporcionar dicha opción en el Blog. Si alguien quiere comentar algo, me puede encontrar en Mastodon y en Diaspora con el nick de Notxor.

También se ha abierto un grupo en Telegram, para usuarios de esa red.

Disculpen las molestias.