If anyone here is still interested: the mod I coded will be updated to 1.21, to fix this bug for vanilla discs without requiring a datapack (though I do recommend using the datapack way if you can!). Additionally, client-side support will be added, meaning you can join your favourite servers with a custom disc resource pack and listen to those custom discs without them being cut off, even if the server doesn't have this mod or any datapacks.
@Chase I usually don't code mods for Forge but since this is such a small mod I might make one if I have time within the next few days
Would Neoforge be okay? I'd rather not code for Forge due to controversies regarding it and Forge mods should be compatible with Neoforge.
@Chase Hi, Minecraft singleplayer worlds run a server on the inside. This means that if you install my mod on your Fabric client, jukeboxes with custom discs will work again in singleplayer worlds. They still will not work on multiplayer, for that to happen the server you are playing on would have to install the mod. Unfortunately I don't think there's a solution for this, unless vanilla Minecraft implements a fix.
Edit: this mod is now available for 1.20.1 as well, and I plan to release a 1.20.5 version as soon as 1.20.5 releases.
I have created a mod that implements a simple fix for this bug. The fix is implemented on the server side, and the mod is currently available for 1.20.4. The mod can be found here, with its source code available here.
The fix is implemented in the JukeboxBlockEntity class, which looks as follows (decompiled Minecraft 1.20.4, using Mojang's official + Parchment MC's mappings):
public class JukeboxBlockEntity extends BlockEntity implements Clearable, ContainerSingleItem {
...
private void stopPlaying() {
this.isPlaying = false;
this.level.gameEvent(GameEvent.JUKEBOX_STOP_PLAY, this.getBlockPos(), GameEvent.Context.of(this.getBlockState()));
this.level.updateNeighborsAt(this.getBlockPos(), this.getBlockState().getBlock());
this.level.levelEvent(1011, this.getBlockPos(), 0);
this.setChanged();
}
private boolean shouldRecordStopPlaying(RecordItem record) {
return this.tickCount >= this.recordStartedTick + (long)record.getLengthInTicks() + 20L;
}
...
}
Usually, when using a jukebox, the stopPlaying method will be called twice: first when the shouldRecordStopPlaying method returns true, and then again when the record item is removed from the jukebox. In the stopPlaying method we can find the following line of code:
this.level.levelEvent(1011, this.getBlockPos(), 0);
This line causes the server to send a level event packet with event code 1011 to the clients, which causes the clients to stop playing the record. The fix I implemented is very simple, and looks as follows:
if (this.getTheItem().isEmpty()) {
this.level.levelEvent(1011, this.getBlockPos(), 0);
}
This fix causes the server to only send the level event packet to the clients once the disc is removed from the jukebox. This stops a custom record from being cut off when the vanilla one would end (which is when the shouldRecordStopPlaying method returns true), but otherwise has no impact on the vanilla behaviour of a jukebox. The only disadvantages I have noticed is that a jukebox stops playing note particles when the vanilla record would end, and that a hopper will remove a record from the jukebox as soon as the vanilla record would end. These could be fixed by implementing @Apollo's fix, with the disadvantage of requiring a custom NBT tag.
Hello, in vanilla gameplay, this issue is unlikely to have any impact. Modified clients however, can send an invalid instance of the
painting_variant
component to the server in serverbound packets where components are sent, which the server will accept at first instance.However, when the server later tries to serialise that component to JSON, NBT, or a different ops (using its normal codec), this will result in an error. This could potentially lead to exploits, however, I haven’t found any in my testing.