Minecraft 1.14.4 dynamical sized integer array

minecraft-commandsminecraft-java-edition

I'm trying to make a dynamical sized integer array in Minecraft 1.14.4. I've already tried different ways, but the problem was with all of those solutions, that I had to summon one entity for each index, and then having two scoreboard, which were holding the integer for every entity and the index for every entity.

Then I had to run something like this:

/execute as @e[type=minecraft:armor_stand,tag=array] if score #index INDEX = @s INDEX store result score #value VALUES run scoreboard players get @s VALUES

where #index is the index I want to read from, and #value is the return value. The problem with this is, that if you have around 1000 indexes, the game gets really laggy due to all the entities.

Has someone got an idea of how to do this in a better way? Maybe with the ability of 1.14 nbt arrays, but without the need of iterating through every index (because every command is taking some time to get executed, and I necessarily need the speed)?

PS.: I'm already using a datapack, so it won't be a problem, if the solution depends on functions.

Best Answer

Creating a dynamically sized array in NBT is somewhat easy: Just use a recursive function to loop over a score (which determines the length) and use /data modify […] append every time. Here is an example:

Setup:

/scoreboard objectives add array dummy
/scoreboard players set $value array 1
/scoreboard players set $length array 10
/scoreboard players set $const2 array 2
/data merge storage fabian:array {array:[]}

I'm using multiplication by 2 on every iteration to show that you can do arbitrary calculations with the values.

Function "fabian:fill_array":

execute store result storage fabian:array value int 1 run scoreboard players get $value array
data modify storage fabian:array array append from storage fabian:array value
scoreboard players operation $value array *= $const2 array
scoreboard players remove $length array 1
execute if score $length array matches 0.. run function fabian:fill_array

Explanation:
The first command copies the scoreboard value of the dummy player "$value" to the "value" NBT tag in storage.
The second command appends the current "value" tag onto the array. There is no way to directly append a scoreboard value to an NBT array.
The rest of the function just does an arbitrary calculation on "$value", ticks down "$length" and loops back to the start as long as "$length" has not reached 0 yet.

Note that this function will still do its calculation once and append that onto the array, even if "$length" is already at 0 or below. You should consider that in whatever system you are using this.


Now the more complicated part: Reading from an arbitrary index. There are two main ways to do this.

The tedious method: Have a big function with every possibility that might occur in your setup:

execute if score $index array matches 0 run data get storage fabian:array array[0]
execute if score $index array matches 1 run data get storage fabian:array array[1]
execute if score $index array matches 2 run data get storage fabian:array array[2]
execute if score $index array matches 3 run data get storage fabian:array array[3]
…

Of course this would be infeasible for very long arrays.

The copy+remove method: Something similar was suggested here (archive), but my method does not require writing the index into the NBT array, so you can use it on already existing data (created e.g. by the game or some other datapack).

First, copy the entire array somewhere else:

/data modify storage fabian:array copy set from storage fabian:array array

Then keep removing the first entry of the array as many times as you want (indicated for example by a scoreboard):

/data remove storage fabian:array copy[0]

Once your loop is over, simply read the value from the first entry that remains:

/data get storage fabian:array copy[0]

You can also do this backwards, for example if you want to read the n-th last entry in an array, run /data remove […] copy[-1] n-1 times and then /data get […] copy[-1].

This can also be used for searching. Tick up an index scoreboard every time you remove the first entry and compare the value to a scoreboard like this:

/execute store result storage fabian:array temp int 1 run scoreboard players get […]
/execute store success score $changed array run data modify storage fabian:array copy[0] set from storage fabian:array temp

The fake player "$changed" will now have a 1 in the array scoreboard if the current array value is different than the score you're comparing it to and 0 if it is the same (so you found the value that you were looking for).
The ticked up scoreboard then indicates the index. Alternatively, you can get the length of the remaining array like this:

/execute store result score $length array if data storage fabian:array array[]

And then you can subtract that from the original length.