Random Minecraft Rocket Generator

January 02, 2020

Minecraft fireworks

I played a bit of vanilla minecraft in survival with a friend and crafted a bunch of fireworks rockets. They are quite expensive, so I cheated in some resources. I wasn't satisfied with the selection and the effort it takes to create a lot of rockets with different colors and shapes by hand. I decided to cheat some in.

I searched online if there's a way to generate random rockets and came across a generator. You have to select a bunch of options and click "generate" to get a string. But I wanted about 100 different rockets.

So I looked into the output, the generator gave me

/give @p firework_rocket{Fireworks:{Flight:2,Explosions:[{Type:1,Flicker:0,Trail:0,Colors:[I;14188952,6719955]},{Type:1,Flicker:0,Trail:1,Colors:[I;11743532,2437522,14188952],FadeColors:[I;14188952]}]}} 1

I quickly decided I am going to write a little ruby script to generate random rockets, because I have a lot of experience in ruby and I can do it with a very little overhead.

I started to cut out the important sections and replaced it with variables

puts "/give @p firework_rocket{Fireworks:{Flight:#{flight},Explosions:#{gen_explosion}}} #{count}"

flight is the equivalent of 1 to 3 gunpowder you add so I defined it as

flight = rand(1..3)

to get a random range between 1 and 3.

I wrote this code to generate the explosion string:

def gen_explosion
    type = rand(0..4)
    flicker = rand(0..1)
    trail = rand(0..1)

    res = ["Type:#{type}","Flicker:#{flicker}","Trail:#{trail}","Colors:#{random_colors(rand(1..3))}"]
    if trail == 1
        res << "FadeColor:#{random_colors(1)}"
    end

    return "{#{res.join(",")}}"
end

A rocket can have multiple explosions and they need to be in square brackets. So I added a method to call gen_explosion 1 to 5 times

def gen_explosions
    res = []
    rand(1..5).times do
        res << gen_explosion
    end
    "[#{res.join(',')}]"
end

I also needed to generate the color. Looking at the string, I figured that the color is a decimal representation of RGB, so we have 255 * 255 * 255 colors that will work. I ended up making a method that generates n colors (similar to what the methods above do, combined it in one) method:

def random_colors(n)
    colors = []
    n.times do
        colors << rand(0..255*255*255)
    end
    return "[I;"+colors.join(",")+"]"
end

This could be a lot fancier. For example I could want to avoid having dark colors. I was okay with random values for now though and I wanted to get it working.

Strings too long

At first in the development process, I messed up having forgotten the square brackets in gen_explosions. After debugging it by comparing it to a good string, I found another issue: When I pasted the output into the chat line in the game, it would cut off the string if it was longer than 255 characters and fail to generate the rocket. I reduced the amount of explosions a rocket has to 3, as well as limit the colors to 3 and one color for the fade effect. It sometimes still generated strings that were too long.

Quick workaround

As a quick workaround I am not particularily proud of; I ended up generating the strings so often until I have a string that is lower or equal 255 characters in size.

def gen_rocket_to_copy
    size = 256
    while size > 255
        res = gen_rocket
        size = res.size
    end
    res
end

Let's count

I was dissatisfied with it and I decided to count the characters. I split a few things into parts and copied it into irb (the interactive ruby shell):

"/give @p firework_rocket{Fireworks:{Flight:1,Explosions:[]}} 64".size
=> 63

So the base command takes 61 characters of space already. I then noticed that there's room for optimization in the gen_explosion method: I can omit values of Flicker and Trail if the random value is 0. So I ended up changing gen_explosion to

def gen_explosion
    type = rand(0..4)
    res = ["Type:#{type}"]

    res << "Flicker:1" if rand(0..1) == 1
    if rand(0..1) == 1
     res << "Trail:1"
     res << "FadeColors:#{random_colors(1)}"
    end
    res << "Colors:#{random_colors(rand(1..3))}"

    return "{#{res.join(",")}}"
end

Back to counting. I limited the color generation to 3 colors. The maximum size it can be can be quickly determined using irb:

"[I;16581375]".size
=> 12
"[I;16581375;16581375]".size
=> 21
"[I;16581375,16581375,16581375]".size
=> 30

And the string per explosion minus the colors:

"{Type:1,Flicker:1,Trail:1,Colors:,FadeColors:}".size
=> 46

Since I set FadeColors to be just one color, the maximum lenght it can have is 46 + 12 (1 fade color) + 30 (3 main colors) + 1 (comma) = 89

I changed the code in gen_explosions to check for if it can fit the remaining length:

def gen_explosions
    res = []
    rand(1..3).times do
        if res.join(",").size <= 255 - 63 - 89
            res << gen_explosion
        end
    end
    "[#{res.join(',')}]"
end

But what if the gen_explosion it generates in this iteration can actually fit in the string? I made it to generate the explsion string first, then compared it if it fits within the boundaries of 255 - 63 - 1 (adding a comma)

def gen_explosions
    res = []
    rand(1..3).times do
        e = gen_explosion
        if e.size + res.join(",").size <= 255 - 63 - 1
            res << e
        end
    end
    "[#{res.join(',')}]"
end

Copying it to clipboard

So instead of having to copy+paste the output from my terminal, I wanted to copy it to my clipboard. I use CopyQ as my clipboard manager, in order to copy it to my clipboard I can easily do it with a call:

system("copyq copy \"#{gen_rocket}\"")

In my first attempt do automatically get more rockets I put it in an endless loop and generating a new rocket every half second

while true
    system("copyq copy \"#{gen_rocket}\"")
    sleep 0.5
end

Auto-Paste

I could have stopped there, but I wanted to not press "t", my paste key combinaton and enter every time. I installed autokey and looked into its documentation. First, i changed my script to output the give command instead of copying it to my clipboard (and removed the endless loop):

puts gen_rocket

In autohotkey I wrote a script that activates on a keypress. I bound it to "z".

res = system.exec_command("~/src/minecraft_fireworks/fireworks.rb", getOutput=True)

keyboard.fake_keypress("t")
time.sleep(0.2)
keyboard.send_keys(res)
time.sleep(0.2)
keyboard.fake_keypress("<enter>")

I couldn't get it it working at first but then I looked up the documentatoin and figured that I needed to add getOutput=True to the system call. I had (and still have) some timing issues. Adding 0.2s delays between they key presses seem to fix most of the issues.

Sources

It has been a fun little project! If you liked liked this little adventure and you're able to, you can sponsor me on github or buy me a coffee.

I pushed the source code to my repository on github: https://github.com/jglauche/mc_fireworks