Minecraft – Is it possible to index raw JSON text, or index multiple NBT path data in one line? (Scanning Horses in Minecraft)

minecraft-commandsminecraft-java-edition

The two questions (around the same principle):

Is it possible to index the newly introduced in 1.14 NBT and Entity raw JSON command (e.g. /tellraw @s {"nbt":"Attributes[0].Base","entity":"@e[type=horse,sort=nearest]"}) so that when it returns the results for multiple entities you can format the results?

If the above isn't possible, is it at least possible to bridge the gap between returning all an entity's data (Attributes[].Base) and only one part of the entity's data (Attributes[0].Base)? i.e. Attributes[0,2,7].Base – this doesn't work, but is there something like it?


Details:

So since a lot of the old horse attribute mods haven't been updated to 1.14, I decided to give building an in game version via commands a spin. Luckily, 1.14 added a really cool feature that allows you to effectively scan the NBT data of a bunch of local entities with the /tellraw command and raw JSON text (thanks to Fabian Röling for providing the details here and here).

An example of how you can use this command to scan horses is by using this command:

/tellraw @s {"nbt":"Attributes[0].Base","entity":"@e[type=horse,sort=nearest]"}

Which returns a list of the health stats of all individual horses, from nearest to farthest:

19.0d, 23.0d, 20.0d, 22.0d, 20.0d, 20.0d, 19.0d, 22.0d, 22.0d, 21.0d, 23.0d, 27.0d, 21.0d, 17.0d, 17.0d, 19.0d

This is great, except there are two more stats we care about with horses, speed and jump height. We can add speed to this line along with some flavor text with the command:

/tellraw @s [{"nbt":"Attributes[0].Base","entity":"@e[type=horse,sort=nearest]"}, " Health ",{"nbt":"Attributes[2].Base 100","entity":"@e[type=horse,sort=nearest]"}, " Speed "]

But the results are less than stellar:

19.0d, 23.0d, 20.0d, 22.0d, 20.0d, 20.0d, 19.0d, 22.0d, 22.0d, 21.0d, 23.0d, 27.0d, 21.0d, 17.0d, 17.0d, 19.0d Health 0.27321843060292755d, 0.21480220332502717d, 0.17749702015266722d, 0.28170043791890875d, 0.24102104888670942d, 0.20762532646388024d, 0.1808260524268614d, 0.2572008833733531d, 0.19745627778622357d, 0.19073958560813306d, 0.21073516590775235d, 0.21222213167329812d, 0.16440016044525307d, 0.2815179975462115d, 0.19912091795196668d, 0.1991595317469814d Speed 

It lists all the horses' health followed by all the horses' speed. This makes sense, but ideally we'd want the result to give us the first horse's health and speed, followed by the second horse's health and speed, and so on, to make it easy to compare the horses.

It should be noted the both lists return the horses in the same order; the horse with 19.0 health is also the one with 0.273218 speed. So if you were able to actually index and format the arrays (or lists, or really strings), you could easily put the correct data together. However, it appears the NBT and Entity commands return their output data as a string, and I haven't been able to figure out how to format it for the life of me (much less turn those doubles into ints).

Is there a way to index and format the output from the NBT and Entity commands like an array?


The second part of this question comes as an off-branch of the first. Ideally it would be best if one could format the output, because then we could make it look pretty and exactly as we liked. However, there is another way of retrieving the NBT data that keeps the individual horse data together. You can just use the Attributes[0].Base, but not put a number in the index: Attributes[].Base. The issue with this is that it returns all the NBT Base data about a horse, which leads to a mess. We're really only interested in the Health (0), Speed (2), and Jump Height (7) of the horse. I gave a stab at trying Attributes[0,2,7].Base, but obviously this didn't work, but it made me consider other methods. I saw there was a way you could also put specific NBT compounds into the index, but I couldn't find good documentation or examples on this.

Is there a way to reference multiple attributes in a single attributes index, i.e. like Attributes[0,2,7].Base, but that works?


Edit: Goal

added this in since I figured I should include what my ideal goal is. This is how I'd like the text to look in the best case:

19 Health 273 Speed 62 Jump
23 Health 214 Speed 73 Jump
20 Health 177 Speed 53 Jump
....

That's what I'm trying to get the text to look like, with each line being a different horse. This is what the first question is about and what I mean when I say formatting. The second question assumes the above is not possible, and tries to work out a compromise.

Another note, doing this with function files, not command blocks, though I imagine it shouldn't matter.

Best Answer

I assume this entire question comes down to "how to print NBT tags A and B of multiple entities like {A1,B1},{A2,B2},{A3,B3} instead of A1,A2,A3,B1,B2,B3". The best way I know to do that is to copy the data over somewhere else in a way that already groups it. Previously you needed a dummy item, because they allow any data in their tag tag. But since 1.15 there is a feature called "storage" that is made specifically for storing custom NBT. So you could create an array of compounds in there.

Unlike the old answer with dummy items, this method requires no preparation at all.

You need to iterate over the horses and create new entries in the array, then fill them (this is also where you can apply sorting, for example by distance). For each horse, do this (until you're out of tagged horses, you can for example do this in a recursive function):

/execute if entity @e[type=horse,tag=!copied] run data modify storage horses HorseAttributes append value {Health:0d,Speed:0d,Jump:0d}
/execute as @e[type=horse,tag=!copied,sort=nearest,limit=1] store result storage horses HorseAttributes[-1].Health double 0.0000005 run data get entity @s Attributes[{Name:"generic.maxHealth"}].Base 2000000
/execute as @e[type=horse,tag=!copied,sort=nearest,limit=1] store result storage horses HorseAttributes[-1].Speed double 0.0000005 run data get entity @s Attributes[{Name:"generic.movementSpeed"}].Base 2000000
/execute as @e[type=horse,tag=!copied,sort=nearest,limit=1] store result storage horses HorseAttributes[-1].Jump double 0.000000001 run data get entity @s Attributes[{Name:"horse.jumpStrength"}].Base 1000000000
/tag @e[type=horse,tag=!copied,sort=nearest,limit=1] add copied

The reason for first scaling the values up and then down again when copying is that for some reason they only get handled as integers. But if you multiply them with a high value first and later divide them by the same number, you get more precision (jumpStrength for example rounds down to 0 almost every time otherwise). The numbers are chosen to give as much accuracy as possible without ever overflowing (different attributes have different maximum values, that's why they differ).
The array index -1 targets the last compound in the list, the one that was just created.

Now, finally, the output:

/tellraw @s {"storage":"horses","nbt":"HorseAttributes"}

The output can for example look like this:

[{Speed:0.2102355d,Health:21.0d,Jump:0.682921423d},{Speed:0.28055749999999996d,Health:24.0d,Jump:0.819681399d},{Speed:0.2159845d,Health:20.0d,Jump:0.653335773d},{Speed:0.2855935d,Health:25.0d,Jump:0.681110673d},{Speed:0.1827385d,Health:17.0d,Jump:0.750580213d}]