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