I wanted to see what else people have done in terms of combining
Minecraft and Emacs. It turns out that you can control Minecraft from
Emacs via mcf if you set enable-rcon=true in your
server.properties (also a good idea to set rcon.password) and you
configure variables like mcf-rcon-password on the Emacs side. It
needed a little tweaking to get it to connect to a remote server, so
I've submitted a pull request. Anyway, since Emacs can talk to
Minecraft and I can write sequences of Minecraft commands as
functions, I thought about turning my Minecraft command books into
something that I could update right from Emacs.
Creating my own datapack was pretty straightforward once I figured out
the directory structure. I needed to put functions in
<world-name>/datapacks/sachac/data/sachac/functions. Inside
<world-name>/datapacks/sachac, I created pack.mcmeta with the
following contents:
Inside <world-name>/datapacks/sachac/data/sachac/functions, I
created a command_book.mcfunction file with the command to give me
the book. I updated my command book function to remove the / from
the beginning.
I used /reload to reload my Minecraft configuration and /datapack
list to confirm that my datapack was loaded. Then /function
sachac:command_book ran the function to give me the command book, so
that all worked out. I replaced the command in the command block with
the function call.
The next step was to update it directly from Emacs, including
reloading. First, I needed a function to give me the filename of a
function file.
(defunmy-minecraft-datapack-function-file-name (world datapack-name function-name)
"Return the filename for a mcfunction file given WORLD, DATAPACK-NAME, and FUNCTION-NAME."
(seq-reduce
(lambda (path subdir) (expand-file-name subdir path))
(list "datapacks"
datapack-name
"data"
datapack-name
"functions"
(concat function-name ".mcfunction"))
world))
I used C-c C-x p (org-set-property) to add a WORLD property to
my Org subtree. For example, my snapshot world is at
/ssh:desktop:~/.minecraft/saves/Snapshot. Then I can get the correct
value within the subtree by using org-entry-get-with-inheritance.
This is how I wrote the command book function for my snapshot world:
So now I can use C-c C-c to execute the Emacs Lisp block and have my
Minecraft world updated. Then I just need to right-click on my command
block's button or run the function in order to get the new version.
I'm looking forward to learning more about mcfunctions so that I can
write a function that automatically replaces the book in everyone's
inventories. Could be fun.
[2023-04-12 Wed]: Remove / from the beginning so that I can use
this in a function. Split book function into JSON and command. Updated effects to hide particles.
[2023-04-10 Mon]: Separated trident into channeling and riptide.
A+ likes playing recent Minecraft snapshots because of the new
features. The modding systems haven't been updated for the snaphots
yet, so we couldn't use mods like JourneyMap to teleport around. I
didn't want to be the keeper of coordinates and be in charge of
teleporting people to various places.
It turns out that you can make clickable books using JSON. I used the
Minecraft book editor to make a prototype book and figure out the
syntax. Then I used a command block to give it to myself in order to
work around the length limits on commands in chat. A+ loved being able
to carry around a book that could teleport her to either of us or to
specified places, change the time of day, clear the weather, and
change game mode. That also meant that I no longer had to type all the
commands to give her water breathing, night vision, or slow falling,
or give her whatever tools she forgot to pack before she headed out.
It was so handy, W- and I got our own copies too.
Manually creating the clickable targets was annoying, especially since
we wanted the book to have slightly different content depending on the
instance we were in. I wanted to be able to specify the contents using
Org Mode tables and generate the JSON for the book using Emacs.
Here's a screenshot:
This is the code to make it:
(defunmy-minecraft-remove-markup (s)
(if (string-match "^[=~]\\(.+?\\)[=~]$" s)
(match-string 1 s)
s))
(defunmy-minecraft-book-json (title author book)
"Generate the JSON for TITLE AUTHOR BOOK.BOOK should be a list of lists of the form (text click-command color)."
(json-encode
`((pages .
,(apply 'vector
(mapcar
(lambda (page)
(json-encode
(apply 'vector
(seq-mapcat
(lambda (command)
(let ((text (my-minecraft-remove-markup (or (elt command 0) "")))
(click (my-minecraft-remove-markup (or (elt command 1) "")))
(color (or (elt command 2) "")))
(unless (or (string-match "^<.*>$" text)
(string-match "^<.*>$" click)
(string-match "^<.*>$" color))
(list
(append
(list (cons 'text text))
(unless (string= click "")
`((clickEvent
(action . "run_command")
(value . ,(concat "/" click)))))
(unless (string= color "")
(list (cons 'color
color))))
(if (string= color "")
'((text . "\n"))
'((text . "\n")
(color . "reset")))))))
page))))
(seq-partition book 14)
)))
(author . ,author)
(title . ,title))))
(defunmy-minecraft-book (title author book)
"Generate a command to put into a command block in order to get a book.Label it with TITLE and AUTHOR.BOOK should be a list of lists of the form (text click-command color).Copy the command text to the kill ring for pasting into a command block."
(let ((s (concat "item replace entity @p weapon.mainhand with written_book"
(my-minecraft-book-json title author book))))
(kill-new s)
s))
With this code, I can generate a simple book like this:
(my-minecraft-book "Simple book""sachac"'(("Daytime""set time 0800")
("Creative""gamemode creative""#0000cd")))
item replace entity @p weapon.mainhand with written_book{"pages":["[{\"text\":\"Daytime\",\"clickEvent\":{\"action\":\"run_command\",\"value\":\"/set time 0800\"}},{\"text\":\"\\n\"},{\"text\":\"Creative\",\"clickEvent\":{\"action\":\"run_command\",\"value\":\"/gamemode creative\"},\"color\":\"#0000cd\"},{\"text\":\"\\n\",\"color\":\"reset\"}]"],"author":"sachac","title":"Simple book"}
To place it in the world:
I changed my server.properties to set enable-command-block=true.
In the game, I used /gamemode creative to switch to creative mode.
I used /give @p minecraft:command_block to give myself a command block.
I right-clicked an empty place to set the block there.
I right-clicked on the command block and pasted in the command.
I added a button.
Then I clicked on the button and it replaced whatever I was holding
with the book. I used item replace instead of give so that it's
easy to replace old versions.
Now producing instance-specific books is just a matter of including
the sections I want, like a table that has coordinates for different
bases in that particular instance.
I thought about making an Org link type for click commands and some
way of exporting that will convert to JSON and keep the whitespace.
That way, I might be able to write longer notes and export them to
Minecraft book JSON for in-game references, such as notes on villager
blocks or potion ingredients. The table + Emacs Lisp approach is
already quite useful for quick shortcuts, though, and it was easy to
write. We'll see if we need more fanciness!