3

I want to put up a dynamic clock on the top of the sidebar so you can see what time it is (in Minecraft). This is how it should be displayed.

enter image description here

The way I use it, is with a scoreboard with the objective id main where I use the variables #hour and #minute. These variables will change every second.

My problem is, I have to use a function that runs 1440 commands each second. This feels like it's an inefficient way to run the game. The code I run in that function is

...
execute if score #hour main matches 7 if score #second main matches 13 run scoreboard objectives modify main displayname {"text":"7:13"}
execute if score #hour main matches 7 if score #second main matches 14 run scoreboard objectives modify main displayname {"text":"7:14"}
...

I tried using a JSON text in such a way that get scores from the other scoreboard, but that won't work somehow. This is the code that didn't work.

/scoreboard objectives modify main displayname {"score":{"name":"#hour", "objective":"main"}, "extra":[{"text":" : "}{"score":{"name":"#minute", "objective":"main"}}]}

Is there any alternative for putting a clock on the display?

6
  • Hm, that last command looks like it should work. In some places (entity name, book, item name or something like that) it should even update automatically. The best idea I currently have would be binary search. That would only run ~15 commands, but it would require generating tons of function files in a rather complicated way. Apr 8 '20 at 20:15
  • Running the last command only gives the string " : ". When I try to apply it to an entity. It gives a similar result. Apr 8 '20 at 20:26
  • There's no comma between the extra json tags. I'm assuming this was a transcription error when writing the question, because you say you get ':' back.
    – AMJ
    Apr 14 '20 at 21:06
  • I have something that (mostly) works and only executes 32 commands for my first two test cases (13:37 and 4:02)! It's not quite done yet, but I'll post an answer soon. (BTW, my previous calculation had a mistake in it, the minimum possible is ~2×15, not ~1×15, plus a bit of overhead. So my solution is already very close to perfect. Apr 24 '20 at 20:42
  • And it's done! But I will first sleep and do some other things before I write an answer, because that will probably be one of my longest answers ever. A highlight already: I wrote Regex to generate Bash scripts to generate Minecraft commands to generate text. And I had to debug that Regex many times. That is my weird definition of a fun activity. Apr 24 '20 at 23:17
2

This was a really complex project. I wrote Regex expressions to generate Bash scripts to generate Minecraft commands to generate text. But it worked …after a while.

This will be a very long answer. I even had to move out parts of it into Pastebin, because StackExchange does not allow posting answers that are as long as I would have liked (even this shortened version fills 43% of the character limit).
If you just want to skip to downloading the datapack, it's here. The names of the fake players and the scoreboard were kept from your example. Run function clock:start to update the scoreboard display name to the correct time.

The idea: Since scoreboard display names apparently do not allow using scoreboards, NBT and so on, you need to actually check every possible value. That requires 24×60=1440 /scoreboard objectives modify commands and there is no way around that, because you need to cover every possible case. You need to have a command for all of those, but you don't actually need to run all of them every time.
Whenever there is a linear range of many possible cases in programming and only one of those is the "right" one, that's a search problem. The most efficient search algorithm for most of these problems is called "binary search" (archive). The idea is to first check whether the value is in the lower or upper half of your range, then to split that half into halves again and so on. By halving the search space on every iteration, you can for example find one element out of a million in just 20 iterations.
This in Minecraft terms means that you can first check whether the "#hour" score is in the range 0..11 or in the range 12..23 and execute one of two functions depending on that. The function for the hour range 0..11 then checks if the hour is within 0..5 or 6..11 and again executes one of two functions depending on that. This way Minecraft needs to execute at most 22 commands to set the time, sometimes even less. (Note that the chat output tells you the wrong number of executed commands, in this case up to 32.)

The process: This is where it gets complicated. First I wrote down the hours to check for and the minutes to check for within each hour, in my own custom format that was quick to write:


0..11
 0..5
  0..2*
   0..1
  3..5*
   3..4
 6..11
  6..8*
   6..7
  9..11*
   9..10
12..23
 12..17
  12..14*
   12..13
  15..17*
   15..16
 18..23
  18..20*
   18..19
  21..23*
   21..22


00..29
 00..14
  00..07
   00..03
    00..01
    02..03
   04..07
    04..05
    06..07
  08..14
   08..11
    08..09
    10..11
   12..14*
    12..13
 15..29
  15..22
   15..18
    15..16
    17..18
   19..22
    19..20
    21..22
  23..29
   23..26
    23..24
    25..26
   27..29*
    27..28
30..59
 30..44
  30..37
   30..33
    30..31
    32..33
   34..37
    34..35
    36..37
  38..44
   38..41
    38..39
    40..41
   42..44*
    42..43
 45..59
  45..52
   45..48
    45..46
    47..48
   49..52
    49..50
    51..52
  53..59
   53..56
    53..54
    55..56
   57..59*
    57..58

This was the only manual part of this process, in which I actually wrote down numbers. The rest is mainly Regex magic and some bash scripting.

In the hours file I did these Regex replacements (in Notepad++):

\n(\d+\.\.\d+)\n (\d+\.\.\d+)([\d\D]*?)\n (\d+\.\.\d+)\n\1,\2,\4\n \2\3\n \4
\n (\d+\.\.\d+)\n (\d+\.\.\d+)([\d\D]*?)\n (\d+\.\.\d+)\n \1,\2,\4\n \2\3\n \4
\n (\d+\.\.)(\d+)\*\n (\d+\.\.\d+)\n \1\2,\3,\2*\n \3
\n (\d+)(\.\.)(\d+)\n \1\2\3,\1*,\3*

In the minutes file I did these Regex replacements:

\n(\d+\.\.\d+)\n (\d+\.\.\d+)([\d\D]*?)\n (\d+\.\.\d+)\n\1,\2,\4\n \2\3\n \4
\n (\d+\.\.\d+)\n (\d+\.\.\d+)([\d\D]*?)\n (\d+\.\.\d+)\n \1,\2,\4\n \2\3\n \4
\n (\d+\.\.\d+)\n (\d+\.\.\d+)([\d\D]*?)\n (\d+\.\.\d+)\n \1,\2,\4\n \2\3\n \4
\n (\d+\.\.\d+)\n (\d+\.\.\d+)([\d\D]*?)\n (\d+\.\.\d+)\n \1,\2,\4\n \2\3\n \4
\n (\d+\.\.)(\d+)\*\n (\d+\.\.\d+)\n \1\2,\3,\2\n \3
\n (\d+)\.\.(\d+)\n \1..\2,\1,\2

I won't explain all of this, mainly because it's complex and a lot of trial and error was involved in getting it right, but you can see that these replacements all look pretty similar. The most special trick I used was matching any character, including newlines, in one spot, but not for every ., by using [\d\D]. I got that from here.
These Regexes essentially say "append the elements with one intendation space step more onto the current one, with commas in between", but also have special handling for cases like 0..2, which should run the function for 0..1 in one case, but only for 2 (without range) in the other case, or even directly set the scoreboard display name (for the minutes file).

This turned the hours file into this: https://pastebin.com/ihY8EfsA (Pastebin apparently removes trailing newlines.)

The start of that looks like this:


0..11,0..5,6..11
 0..5,0..2,3..5
  0..2,0..1,2*
   0..1,0*,1*
  3..5,3..4,5*

The minutes file got turned into this: https://pastebin.com/cY6CPPND

The start of that looks like this:


00..29,00..14,15..29
 00..14,00..07,08..14
  00..07,00..03,04..07
   00..03,00..01,02..03
    00..01,00,01
    02..03,02,03
   04..07,04..05,06..07

Then I ran another round of Regexes, these ones already containing some Bash syntax and some Minecraft syntax:

Hours file:

*(\d+)\.\.(\d+)\,(\d+)\.\.(\d+)\,(\d+)\.\.(\d+)touch h_\1_\2.mcfunction\necho "execute if score #hour main matches \3..\4 run function clock:h_\3_\4" >> h_\1_\2.mcfunction\necho "execute if score #hour main matches \5..\6 run function clock:h_\5_\6" >> h_\1_\2.mcfunction
*(\d+)\.\.(\d+)\,(\d+)\.\.(\d+)\,(\d+)\*touch h_\1_\2.mcfunction\necho "execute if score #hour main matches \3..\4 run function clock:h_\3_\4" >> h_\1_\2.mcfunction\necho "execute if score #hour main matches \5 run function clock:h_\5" >> h_\1_\2.mcfunction
*(\d+)\.\.(\d+)\,(\d+)\*\,(\d+)\*touch h_\1_\2.mcfunction\necho "execute if score #hour main matches \3 run function clock:h_\3" >> h_\1_\2.mcfunction\necho "execute if score #hour main matches \4 run function clock:h_\4" >> h_\1_\2.mcfunction

Minutes file:

*(\d+)\.\.(\d+)\,(\d+)\.\.(\d+)\,(\d+)\.\.(\d+)for\(\(a=0;a<=23;a++\)\); do\ntouch "h_"$a"_m_\1_\2.mcfunction"\necho "execute if score #minute main matches \3..\4 run function clock:h_"$a"_m_\3_\4" >> "h_"$a"_m_\1_\2.mcfunction"\necho "execute if score #minute main matches \5..\6 run function clock:h_"$a"_m_\5_\6" >> "h_"$a"_m_\1_\2.mcfunction"\ndone
*(\d+)\.\.(\d+)\,(\d+)\.\.(\d+)\,(\d+)for\(\(a=0;a<=23;a++\)\); do\ntouch "h_"$a"_m_\1_\2.mcfunction"\necho "execute if score #minute main matches \3..\4 run function clock:h_"$a"_m_\3_\4" >> "h_"$a"_m_\1_\2.mcfunction"\necho "execute if score #minute main matches \5 run scoreboard objectives modify main displayname {\\\"text\\\":\\\""$a":\5\\\"}" >> "h_"$a"_m_\1_\2.mcfunction"\ndone
*(\d+)\.\.(\d+)\,(\d+),(\d+)for\(\(a=0;a<=23;a++\)\); do\ntouch "h_"$a"_m_\1_\2.mcfunction"\necho "execute if score #minute main matches \3 run scoreboard objectives modify main displayname {\\\"text\\\":\\\""$a":\3\\\"}" >> "h_"$a"_m_\1_\2.mcfunction"\necho "execute if score #minute main matches \4 run scoreboard objectives modify main displayname {\\\"text\\\":\\\""$a":\4\\\"}" >> "h_"$a"_m_\1_\2.mcfunction"\ndone

There are triple backslashes in there, to double-escape quotation marks, followed directly by non-escaped quotation marks, also Regex references mixed with Bash variables and Minecraft scoreboards. This was a weird project.
These Regexes ignore the indentation, that was only needed for the first step. They find the three possible formats 0..0,0..0,0..0, 0..0,0..0,0* and 0..0,0*,0* (or apparently also without asterisks, in the minutes file, which I only noticed while writing this answer) and write the corresponding Bash commands to create the function files (named after the range, for less confusion) and fill them with Minecraft commands that check the two possible ranges each and do the corresponding action.

I saved the files that were created that way as...

hours.sh: https://pastebin.com/kYsm50nu

… which begins like this:


touch h_0_11.mcfunction
echo "execute if score #hour main matches 0..5 run function clock:h_0_5" >> h_0_11.mcfunction
echo "execute if score #hour main matches 6..11 run function clock:h_6_11" >> h_0_11.mcfunction
touch h_0_5.mcfunction
echo "execute if score #hour main matches 0..2 run function clock:h_0_2" >> h_0_5.mcfunction
echo "execute if score #hour main matches 3..5 run function clock:h_3_5" >> h_0_5.mcfunction

… and minutes.sh: https://pastebin.com/wjP8Rgex

… which begins like this:


for((a=0;a<=23;a++)); do
touch "h_"$a"_m_00_29.mcfunction"
echo "execute if score #minute main matches 00..14 run function clock:h_"$a"_m_00_14" >> "h_"$a"_m_00_29.mcfunction"
echo "execute if score #minute main matches 15..29 run function clock:h_"$a"_m_15_29" >> "h_"$a"_m_00_29.mcfunction"
done
for((a=0;a<=23;a++)); do
touch "h_"$a"_m_00_14.mcfunction"
echo "execute if score #minute main matches 00..07 run function clock:h_"$a"_m_00_07" >> "h_"$a"_m_00_14.mcfunction"
echo "execute if score #minute main matches 08..14 run function clock:h_"$a"_m_08_14" >> "h_"$a"_m_00_14.mcfunction"
done

… and ends like this:

for((a=0;a<=23;a++)); do
touch "h_"$a"_m_57_59.mcfunction"
echo "execute if score #minute main matches 57..58 run function clock:h_"$a"_m_57_58" >> "h_"$a"_m_57_59.mcfunction"
echo "execute if score #minute main matches 59 run scoreboard objectives modify main displayname {\"text\":\""$a":59\"}" >> "h_"$a"_m_57_59.mcfunction"
done
for((a=0;a<=23;a++)); do
touch "h_"$a"_m_57_58.mcfunction"
echo "execute if score #minute main matches 57 run scoreboard objectives modify main displayname {\"text\":\""$a":57\"}" >> "h_"$a"_m_57_58.mcfunction"
echo "execute if score #minute main matches 58 run scoreboard objectives modify main displayname {\"text\":\""$a":58\"}" >> "h_"$a"_m_57_58.mcfunction"
done

Then I created another file: https://pastebin.com/MMGPew4f
I called it hour_minutes.sh and in it I typed one Bash command manually, copied it 23 times and used Notepad++'s "column editor" to replace the constant number with one that counted up. This file links up the hours and minutes, this special case was easier to handle separately.

This file starts like this:

touch h_0.mcfunction; echo "execute if score #minute main matches 0..29 run function clock:h_0_m_00_29" >> h_0.mcfunction; echo "execute if score #minute main matches 30..59 run function clock:h_0_m_30_59" >> h_0.mcfunction
touch h_1.mcfunction; echo "execute if score #minute main matches 0..29 run function clock:h_1_m_00_29" >> h_1.mcfunction; echo "execute if score #minute main matches 30..59 run function clock:h_1_m_30_59" >> h_1.mcfunction

Now the only task left was to create the skeleton for the datapack (pack.mcmeta and the folders), put the three scripts into the function folder and execute them there with a Linux console. That created 1438 function files, each with only two commands inside, most of those referencing other functions.

Of course it wasn't actually that straightforward, I had a lot of problems in the meantime, for example a 12 instead of a 13 in the initial notes cascaded down the process and caused Minecraft to get stuck in an infinite loop of self-referencing functions, I found out about strange Regex behaviours that at first seemed like bugs in regex itself to me and much more, but eventually I figured it out and the way I wrote it here was the main workflow, which I repeated every time I wanted to re-create the datapack. You could follow the process from this answer or use the .sh files in a Linux console yourself if you wanted to re-create the datapack with your own modifications.

I tried to do a performance comparison between this and the 1440-commands-method, but for some reason the Alt+F3 graph seems to completely ignore the lag spikes from both methods, even if I execute them thousands of times at once and the server freezes for multiple seconds. So you'll just have to trust me that executing 22 commands is better for performance than executing 1440 commands. :D

Here is at least a screenshot of the result of executing the function:

LEET

And again, you can download the datapack from here: https://drive.google.com/file/d/1OKsOyeUba1ywYejct6RvdQobxDes6WIW

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.