From afe3e78b0f9c944a770bf57bb17348a794e2f84b Mon Sep 17 00:00:00 2001 From: Semmieboy YT <58337334+SemmieboyYT@users.noreply.github.com> Date: Sun, 6 Mar 2022 03:10:35 +0100 Subject: [PATCH] Switched Java version to 8 so the mod works on more Minecraft versions Made the loading of songs multithreaded, decreasing startup time of the client Fixed a bug where incorrect instruments were getting played Fixed a bug where the blocks overlay was unable to be closed Fixed a bug where the mod would continue playing the song after switching worlds Fixed a bug where note blocks with a block above them were seen as a valid note block --- build.gradle | 2 +- gradle.properties | 2 +- .../disc_jockey/BinaryReader.java | 10 +- .../disc_jockey/DiscjockeyCommand.java | 17 +- .../java/semmieboy_yt/disc_jockey/Main.java | 14 ++ .../java/semmieboy_yt/disc_jockey/Note.java | 78 ++++++--- .../semmieboy_yt/disc_jockey/SongLoader.java | 158 +++++++++--------- .../semmieboy_yt/disc_jockey/SongPlayer.java | 12 +- .../gui/screen/DiscJockeyScreen.java | 53 +++--- .../assets/disc_jockey/lang/en_us.json | 3 +- 10 files changed, 210 insertions(+), 139 deletions(-) diff --git a/build.gradle b/build.gradle index 3b822ea..fc891b1 100644 --- a/build.gradle +++ b/build.gradle @@ -33,7 +33,7 @@ processResources { } } -def targetJavaVersion = 17 +def targetJavaVersion = 8 tasks.withType(JavaCompile).configureEach { // ensure that the encoding is set to UTF-8, no matter what the system default is // this fixes some edge cases with special characters not displaying correctly diff --git a/gradle.properties b/gradle.properties index 85f051b..a137614 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,7 +6,7 @@ minecraft_version=1.18.1 yarn_mappings=1.18.1+build.22 loader_version=0.13.2 # Mod Properties -mod_version=1.0.0 +mod_version=1.0.1 maven_group=semmieboy_yt archives_base_name=disc_jockey # Dependencies diff --git a/src/main/java/semmieboy_yt/disc_jockey/BinaryReader.java b/src/main/java/semmieboy_yt/disc_jockey/BinaryReader.java index 133946b..01aa14c 100644 --- a/src/main/java/semmieboy_yt/disc_jockey/BinaryReader.java +++ b/src/main/java/semmieboy_yt/disc_jockey/BinaryReader.java @@ -15,7 +15,7 @@ public class BinaryReader { } public int readInt() throws IOException { - return buffer.clear().put(readBytes(Integer.BYTES)).rewind().getInt(); + return ((ByteBuffer)((ByteBuffer)buffer.clear()).put(readBytes(Integer.BYTES)).rewind()).getInt(); } public long readUInt() throws IOException { @@ -27,7 +27,7 @@ public class BinaryReader { } public short readShort() throws IOException { - return buffer.clear().put(readBytes(2)).rewind().getShort(); + return ((ByteBuffer)((ByteBuffer)buffer.clear()).put(readBytes(Short.BYTES)).rewind()).getShort(); } public String readString() throws IOException { @@ -35,7 +35,7 @@ public class BinaryReader { } public float readFloat() throws IOException { - return buffer.clear().put(readBytes(4)).rewind().getFloat(); + return ((ByteBuffer)((ByteBuffer)buffer.clear()).put(readBytes(Float.BYTES)).rewind()).getFloat(); } /*private int getStringLength() throws IOException { @@ -60,6 +60,8 @@ public class BinaryReader { } public byte[] readBytes(int length) throws IOException { - return in.readNBytes(length); + byte[] bytes = new byte[length]; + for (int i = 0; i < length; i++) bytes[i] = readByte(); + return bytes; } } diff --git a/src/main/java/semmieboy_yt/disc_jockey/DiscjockeyCommand.java b/src/main/java/semmieboy_yt/disc_jockey/DiscjockeyCommand.java index 5d9a9d9..6334fbe 100644 --- a/src/main/java/semmieboy_yt/disc_jockey/DiscjockeyCommand.java +++ b/src/main/java/semmieboy_yt/disc_jockey/DiscjockeyCommand.java @@ -1,7 +1,9 @@ package semmieboy_yt.disc_jockey; import net.fabricmc.fabric.api.client.command.v1.ClientCommandManager; +import net.fabricmc.fabric.api.client.command.v1.FabricClientCommandSource; import net.minecraft.client.MinecraftClient; +import net.minecraft.text.TranslatableText; import semmieboy_yt.disc_jockey.gui.screen.DiscJockeyScreen; import static net.fabricmc.fabric.api.client.command.v1.ClientCommandManager.literal; @@ -11,13 +13,22 @@ public class DiscjockeyCommand { ClientCommandManager.DISPATCHER.register( literal("discjockey") .executes(context -> { - MinecraftClient client = context.getSource().getClient(); - client.send(() -> client.setScreen(new DiscJockeyScreen())); + FabricClientCommandSource source = context.getSource(); + if (SongLoader.loadingSongs) { + source.sendError(new TranslatableText(Main.MOD_ID+".still_loading")); + } else { + MinecraftClient client = source.getClient(); + client.send(() -> client.setScreen(new DiscJockeyScreen())); + } return 1; }) .then(literal("reload") .executes(context -> { - SongLoader.loadSongs(); + if (SongLoader.loadingSongs) { + context.getSource().sendError(new TranslatableText(Main.MOD_ID+".still_loading")); + } else { + SongLoader.loadSongs(); + } return 1; }) ) diff --git a/src/main/java/semmieboy_yt/disc_jockey/Main.java b/src/main/java/semmieboy_yt/disc_jockey/Main.java index a73d1a6..22ca5bb 100644 --- a/src/main/java/semmieboy_yt/disc_jockey/Main.java +++ b/src/main/java/semmieboy_yt/disc_jockey/Main.java @@ -5,6 +5,8 @@ import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; import net.fabricmc.fabric.api.client.networking.v1.ClientLoginConnectionEvents; import net.fabricmc.fabric.api.client.rendering.v1.HudRenderCallback; import net.fabricmc.loader.api.FabricLoader; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.world.ClientWorld; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import semmieboy_yt.disc_jockey.gui.hud.BlocksOverlay; @@ -30,6 +32,18 @@ public class Main implements ClientModInitializer { SongLoader.loadSongs(); + ClientTickEvents.START_CLIENT_TICK.register(new ClientTickEvents.StartTick() { + private ClientWorld prevWorld; + + @Override + public void onStartTick(MinecraftClient client) { + if (prevWorld != client.world) { + PREVIEWER.stop(); + SONG_PLAYER.stop(); + } + prevWorld = client.world; + } + }); ClientTickEvents.START_WORLD_TICK.register(world -> { for (ClientTickEvents.StartWorldTick listener : TICK_LISTENERS) listener.onStartTick(world); }); diff --git a/src/main/java/semmieboy_yt/disc_jockey/Note.java b/src/main/java/semmieboy_yt/disc_jockey/Note.java index 0e3a0ef..223403e 100644 --- a/src/main/java/semmieboy_yt/disc_jockey/Note.java +++ b/src/main/java/semmieboy_yt/disc_jockey/Note.java @@ -4,31 +4,67 @@ import net.minecraft.block.Block; import net.minecraft.block.Blocks; import net.minecraft.block.enums.Instrument; -import java.util.Map; +import java.util.HashMap; -public record Note(Instrument instrument, byte note) { - public static final Map INSTRUMENT_BLOCKS = Map.ofEntries( - Map.entry(Instrument.HARP, Blocks.AIR), - Map.entry(Instrument.BASEDRUM, Blocks.STONE), - Map.entry(Instrument.SNARE, Blocks.SAND), - Map.entry(Instrument.HAT, Blocks.GLASS), - Map.entry(Instrument.BASS, Blocks.OAK_PLANKS), - Map.entry(Instrument.FLUTE, Blocks.CLAY), - Map.entry(Instrument.BELL, Blocks.GOLD_BLOCK), - Map.entry(Instrument.GUITAR, Blocks.WHITE_WOOL), - Map.entry(Instrument.CHIME, Blocks.PACKED_ICE), - Map.entry(Instrument.XYLOPHONE, Blocks.BONE_BLOCK), - Map.entry(Instrument.IRON_XYLOPHONE, Blocks.IRON_BLOCK), - Map.entry(Instrument.COW_BELL, Blocks.SOUL_SAND), - Map.entry(Instrument.DIDGERIDOO, Blocks.PUMPKIN), - Map.entry(Instrument.BIT, Blocks.EMERALD_BLOCK), - Map.entry(Instrument.BANJO, Blocks.HAY_BLOCK), - Map.entry(Instrument.PLING, Blocks.GLOWSTONE) - ); +public class Note { + public static final HashMap INSTRUMENT_BLOCKS = new HashMap<>(); public static final byte LAYER_SHIFT = Short.SIZE; public static final byte INSTRUMENT_SHIFT = Short.SIZE * 2; public static final byte NOTE_SHIFT = Short.SIZE * 2 + Byte.SIZE; - public static final Instrument[] INSTRUMENTS = Instrument.values(); + public static final Instrument[] INSTRUMENTS = new Instrument[] { + Instrument.HARP, + Instrument.BASS, + Instrument.BASEDRUM, + Instrument.SNARE, + Instrument.HAT, + Instrument.GUITAR, + Instrument.FLUTE, + Instrument.BELL, + Instrument.CHIME, + Instrument.XYLOPHONE, + Instrument.IRON_XYLOPHONE, + Instrument.COW_BELL, + Instrument.DIDGERIDOO, + Instrument.BIT, + Instrument.BANJO, + Instrument.PLING + }; + + static { + INSTRUMENT_BLOCKS.put(Instrument.HARP, Blocks.AIR); + INSTRUMENT_BLOCKS.put(Instrument.BASEDRUM, Blocks.STONE); + INSTRUMENT_BLOCKS.put(Instrument.SNARE, Blocks.SAND); + INSTRUMENT_BLOCKS.put(Instrument.HAT, Blocks.GLASS); + INSTRUMENT_BLOCKS.put(Instrument.BASS, Blocks.OAK_PLANKS); + INSTRUMENT_BLOCKS.put(Instrument.FLUTE, Blocks.CLAY); + INSTRUMENT_BLOCKS.put(Instrument.BELL, Blocks.GOLD_BLOCK); + INSTRUMENT_BLOCKS.put(Instrument.GUITAR, Blocks.WHITE_WOOL); + INSTRUMENT_BLOCKS.put(Instrument.CHIME, Blocks.PACKED_ICE); + INSTRUMENT_BLOCKS.put(Instrument.XYLOPHONE, Blocks.BONE_BLOCK); + INSTRUMENT_BLOCKS.put(Instrument.IRON_XYLOPHONE, Blocks.IRON_BLOCK); + INSTRUMENT_BLOCKS.put(Instrument.COW_BELL, Blocks.SOUL_SAND); + INSTRUMENT_BLOCKS.put(Instrument.DIDGERIDOO, Blocks.PUMPKIN); + INSTRUMENT_BLOCKS.put(Instrument.BIT, Blocks.EMERALD_BLOCK); + INSTRUMENT_BLOCKS.put(Instrument.BANJO, Blocks.HAY_BLOCK); + INSTRUMENT_BLOCKS.put(Instrument.PLING, Blocks.GLOWSTONE); + } + + public final Instrument instrument; + public final byte note; + + public Note(Instrument instrument, byte note) { + this.instrument = instrument; + this.note = note; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof Note) { + Note note = (Note)obj; + return note.note == this.note && note.instrument == instrument; + } + return false; + } } diff --git a/src/main/java/semmieboy_yt/disc_jockey/SongLoader.java b/src/main/java/semmieboy_yt/disc_jockey/SongLoader.java index f6edf4e..417deb0 100644 --- a/src/main/java/semmieboy_yt/disc_jockey/SongLoader.java +++ b/src/main/java/semmieboy_yt/disc_jockey/SongLoader.java @@ -4,98 +4,100 @@ import semmieboy_yt.disc_jockey.gui.SongListWidget; import java.io.File; import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; public class SongLoader { public static final ArrayList SONGS = new ArrayList<>(); + public static volatile boolean loadingSongs; public static void loadSongs() { - SONGS.clear(); - for (File file : Main.songsFolder.listFiles()) { - if (file.isFile()) { - try { - BinaryReader reader = new BinaryReader(new FileInputStream(file)); - Song song = new Song(); + if (loadingSongs) return; + new Thread(() -> { + loadingSongs = true; + SONGS.clear(); + for (File file : Main.songsFolder.listFiles()) { + if (file.isFile()) { + try { + BinaryReader reader = new BinaryReader(new FileInputStream(file)); + Song song = new Song(); - song.fileName = file.getName(); + song.fileName = file.getName(); - song.length = reader.readShort(); - - boolean newFormat = song.length == 0; - if (newFormat) { - song.formatVersion = reader.readByte(); - song.vanillaInstrumentCount = reader.readByte(); song.length = reader.readShort(); - } - song.height = reader.readShort(); - song.name = reader.readString(); - song.author = reader.readString(); - song.originalAuthor = reader.readString(); - song.description = reader.readString(); - song.tempo = reader.readShort(); - song.autoSaving = reader.readByte(); - song.autoSavingDuration = reader.readByte(); - song.timeSignature = reader.readByte(); - song.minutesSpent = reader.readInt(); - song.leftClicks = reader.readInt(); - song.rightClicks = reader.readInt(); - song.blocksAdded = reader.readInt(); - song.blocksRemoved = reader.readInt(); - song.importFileName = reader.readString(); - - if (newFormat) { - song.loop = reader.readByte(); - song.maxLoopCount = reader.readByte(); - song.loopStartTick = reader.readShort(); - } - - song.entry = new SongListWidget.SongEntry(song.name.isBlank() ? song.fileName : song.name+" ("+song.fileName+")", SONGS.size()); - song.searchableFileName = song.fileName.toLowerCase().replaceAll("\\s", ""); - song.searchableName = song.name.toLowerCase().replaceAll("\\s", ""); - - short tick = -1; - short jumps; - while ((jumps = reader.readShort()) != 0) { - tick += jumps; - short layer = -1; - while ((jumps = reader.readShort()) != 0) { - layer += jumps; - - byte instrumentId = reader.readByte(); - byte noteId = (byte)(reader.readByte() - 33); - - if (newFormat) { - // Data that is not needed as it only works with commands - reader.readByte(); // Velocity - reader.readByte(); // Panning - reader.readShort(); // Pitch - } - - if (noteId < 0) { - noteId = 0; - } else if (noteId > 24) { - noteId = 24; - } - - Note note = new Note(Note.INSTRUMENTS[instrumentId], noteId); - if (!song.uniqueNotes.contains(note)) song.uniqueNotes.add(note); - - song.notes = Arrays.copyOf(song.notes, song.notes.length + 1); - song.notes[song.notes.length - 1] = tick | layer << Note.LAYER_SHIFT | (long)instrumentId << Note.INSTRUMENT_SHIFT | (long)noteId << Note.NOTE_SHIFT; + boolean newFormat = song.length == 0; + if (newFormat) { + song.formatVersion = reader.readByte(); + song.vanillaInstrumentCount = reader.readByte(); + song.length = reader.readShort(); } - } - SONGS.add(song); - } catch (FileNotFoundException ignored) { - // Won't be thrown - } catch (IOException exception) { - Main.LOGGER.error("Unable to read song "+file.getName(), exception); + song.height = reader.readShort(); + song.name = reader.readString(); + song.author = reader.readString(); + song.originalAuthor = reader.readString(); + song.description = reader.readString(); + song.tempo = reader.readShort(); + song.autoSaving = reader.readByte(); + song.autoSavingDuration = reader.readByte(); + song.timeSignature = reader.readByte(); + song.minutesSpent = reader.readInt(); + song.leftClicks = reader.readInt(); + song.rightClicks = reader.readInt(); + song.blocksAdded = reader.readInt(); + song.blocksRemoved = reader.readInt(); + song.importFileName = reader.readString(); + + if (newFormat) { + song.loop = reader.readByte(); + song.maxLoopCount = reader.readByte(); + song.loopStartTick = reader.readShort(); + } + + song.entry = new SongListWidget.SongEntry(song.name.replaceAll("\\s", "").isEmpty() ? song.fileName : song.name+" ("+song.fileName+")", SONGS.size()); + song.searchableFileName = song.fileName.toLowerCase().replaceAll("\\s", ""); + song.searchableName = song.name.toLowerCase().replaceAll("\\s", ""); + + short tick = -1; + short jumps; + while ((jumps = reader.readShort()) != 0) { + tick += jumps; + short layer = -1; + while ((jumps = reader.readShort()) != 0) { + layer += jumps; + + byte instrumentId = reader.readByte(); + byte noteId = (byte)(reader.readByte() - 33); + + if (newFormat) { + // Data that is not needed as it only works with commands + reader.readByte(); // Velocity + reader.readByte(); // Panning + reader.readShort(); // Pitch + } + + if (noteId < 0) { + noteId = 0; + } else if (noteId > 24) { + noteId = 24; + } + + Note note = new Note(Note.INSTRUMENTS[instrumentId], noteId); + if (!song.uniqueNotes.contains(note)) song.uniqueNotes.add(note); + + song.notes = Arrays.copyOf(song.notes, song.notes.length + 1); + song.notes[song.notes.length - 1] = tick | layer << Note.LAYER_SHIFT | (long)instrumentId << Note.INSTRUMENT_SHIFT | (long)noteId << Note.NOTE_SHIFT; + } + } + + SONGS.add(song); + } catch (Throwable exception) { + Main.LOGGER.error("Unable to read song "+file.getName(), exception); + } } } - } + loadingSongs = false; + }).start(); } } diff --git a/src/main/java/semmieboy_yt/disc_jockey/SongPlayer.java b/src/main/java/semmieboy_yt/disc_jockey/SongPlayer.java index 8b467f5..61acb38 100644 --- a/src/main/java/semmieboy_yt/disc_jockey/SongPlayer.java +++ b/src/main/java/semmieboy_yt/disc_jockey/SongPlayer.java @@ -67,10 +67,10 @@ public class SongPlayer implements ClientTickEvents.StartWorldTick { BlockPos blockPos = new BlockPos(pos); if (playerPos.squaredDistanceTo(pos) < 4.5 * 4.5) { BlockState blockState = world.getBlockState(blockPos); - if (blockState.isOf(Blocks.NOTE_BLOCK)) { + if (blockState.isOf(Blocks.NOTE_BLOCK) && world.isAir(blockPos.up())) { for (Note note : song.uniqueNotes) { - if (!capturedNotes.contains(note) && blockState.get(Properties.INSTRUMENT) == note.instrument()) { - getNotes(note.instrument()).put(note.note(), blockPos); + if (!capturedNotes.contains(note) && blockState.get(Properties.INSTRUMENT) == note.instrument) { + getNotes(note.instrument).put(note.note, blockPos); capturedNotes.add(note); break; } @@ -89,7 +89,7 @@ public class SongPlayer implements ClientTickEvents.StartWorldTick { HashMap missing = new HashMap<>(); for (Note note : missingNotes) { - Block block = Note.INSTRUMENT_BLOCKS.get(note.instrument()); + Block block = Note.INSTRUMENT_BLOCKS.get(note.instrument); Integer got = missing.get(block); if (got == null) got = 0; missing.put(block, got + 1); @@ -107,11 +107,11 @@ public class SongPlayer implements ClientTickEvents.StartWorldTick { MinecraftClient client = MinecraftClient.getInstance(); int tuneAmount = 0; for (Note note : song.uniqueNotes) { - BlockPos blockPos = noteBlocks.get(note.instrument()).get(note.note()); + BlockPos blockPos = noteBlocks.get(note.instrument).get(note.note); BlockState blockState = world.getBlockState(blockPos); if (blockState.contains(Properties.NOTE)) { - if (blockState.get(Properties.NOTE) != note.note()) { + if (blockState.get(Properties.NOTE) != note.note) { if (client.player.getEyePos().squaredDistanceTo(Vec3d.ofCenter(blockPos, 0.5)) >= 4.5 * 4.5) { stop(); client.inGameHud.getChatHud().addMessage(new TranslatableText(Main.MOD_ID+".player.to_far").formatted(Formatting.RED)); diff --git a/src/main/java/semmieboy_yt/disc_jockey/gui/screen/DiscJockeyScreen.java b/src/main/java/semmieboy_yt/disc_jockey/gui/screen/DiscJockeyScreen.java index a6500ac..c7e57a5 100644 --- a/src/main/java/semmieboy_yt/disc_jockey/gui/screen/DiscJockeyScreen.java +++ b/src/main/java/semmieboy_yt/disc_jockey/gui/screen/DiscJockeyScreen.java @@ -68,36 +68,41 @@ public class DiscJockeyScreen extends Screen { addDrawableChild(previewButton); addDrawableChild(new ButtonWidget(width / 2 + 60, height - 61, 100, 20, new TranslatableText(Main.MOD_ID+".screen.blocks"), button -> { - SongListWidget.SongEntry entry = songListWidget.getSelectedOrNull(); - if (entry != null) { - client.setScreen(null); - Song song = SongLoader.SONGS.get(entry.index); + if (BlocksOverlay.itemStacks == null) { + SongListWidget.SongEntry entry = songListWidget.getSelectedOrNull(); + if (entry != null) { + client.setScreen(null); + Song song = SongLoader.SONGS.get(entry.index); - BlocksOverlay.itemStacks = new ItemStack[0]; - BlocksOverlay.amounts = new int[0]; - BlocksOverlay.amountOfNoteBlocks = song.uniqueNotes.size(); + BlocksOverlay.itemStacks = new ItemStack[0]; + BlocksOverlay.amounts = new int[0]; + BlocksOverlay.amountOfNoteBlocks = song.uniqueNotes.size(); - for (Note note : song.uniqueNotes) { - ItemStack itemStack = Note.INSTRUMENT_BLOCKS.get(note.instrument()).asItem().getDefaultStack(); - int index = -1; + for (Note note : song.uniqueNotes) { + ItemStack itemStack = Note.INSTRUMENT_BLOCKS.get(note.instrument).asItem().getDefaultStack(); + int index = -1; - for (int i = 0; i < BlocksOverlay.itemStacks.length; i++) { - if (BlocksOverlay.itemStacks[i].getItem() == itemStack.getItem()) { - index = i; - break; + for (int i = 0; i < BlocksOverlay.itemStacks.length; i++) { + if (BlocksOverlay.itemStacks[i].getItem() == itemStack.getItem()) { + index = i; + break; + } + } + + if (index == -1) { + BlocksOverlay.itemStacks = Arrays.copyOf(BlocksOverlay.itemStacks, BlocksOverlay.itemStacks.length + 1); + BlocksOverlay.amounts = Arrays.copyOf(BlocksOverlay.amounts, BlocksOverlay.amounts.length + 1); + + BlocksOverlay.itemStacks[BlocksOverlay.itemStacks.length - 1] = itemStack; + BlocksOverlay.amounts[BlocksOverlay.amounts.length - 1] = 1; + } else { + BlocksOverlay.amounts[index] = BlocksOverlay.amounts[index] + 1; } } - - if (index == -1) { - BlocksOverlay.itemStacks = Arrays.copyOf(BlocksOverlay.itemStacks, BlocksOverlay.itemStacks.length + 1); - BlocksOverlay.amounts = Arrays.copyOf(BlocksOverlay.amounts, BlocksOverlay.amounts.length + 1); - - BlocksOverlay.itemStacks[BlocksOverlay.itemStacks.length - 1] = itemStack; - BlocksOverlay.amounts[BlocksOverlay.amounts.length - 1] = 1; - } else { - BlocksOverlay.amounts[index] = BlocksOverlay.amounts[index] + 1; - } } + } else { + BlocksOverlay.itemStacks = null; + client.setScreen(null); } })); diff --git a/src/main/resources/assets/disc_jockey/lang/en_us.json b/src/main/resources/assets/disc_jockey/lang/en_us.json index 00418f9..335e299 100644 --- a/src/main/resources/assets/disc_jockey/lang/en_us.json +++ b/src/main/resources/assets/disc_jockey/lang/en_us.json @@ -10,5 +10,6 @@ "disc_jockey.screen.search": "Search For Songs", "disc_jockey.player.invalid_note_blocks": "The Note Blocks near you are not in the correct configuration. Missing:", "disc_jockey.player.invalid_game_mode": "You can't play in %s", - "disc_jockey.player.to_far": "You went to far away" + "disc_jockey.player.to_far": "You went to far away", + "disc_jockey.still_loading": "The songs are still loading" } \ No newline at end of file