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.