diff --git a/CHANGELOG.md b/CHANGELOG.md index 830df65..acf34e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,15 @@ Ce journal suit les **versions du modpack**. Lorsqu'un seul module change, sa ve - Depuis `26.2.0-alpha.6`, le pack et les modules ont des versions indépendantes. I Like To Move It et Only Fun deviennent les cinquième et sixième modules actifs dans l'alpha 46. - Les versions ci-dessous ont été reconstituées à partir des JAR archivés, des packs locaux et du code source actuel. Les dates n'étant pas enregistrées de manière fiable, elles ne sont pas inventées. +## Modifications non distribuées + +Module modifié : **Ambiance `0.0.0-alpha.17`**. + +- attend de façon bornée la synchronisation client du jukebox avant de charger un disque blanc, ce qui rétablit sa lecture en solo ; +- affiche immédiatement le chargement puis un échec explicite si le jukebox n'est toujours pas synchronisé après cinq secondes ; +- accepte toujours les liens de partage YouTube avec paramètres, notamment `https://youtu.be/Ue5ZBe-GzSM?si=…` ; +- conserve les données Ambiance v2 et le protocole réseau 6, sans migration de sauvegarde. + ## `26.2.0-alpha.213` Module modifié : **Ambiance `0.0.0-alpha.16`**. Les neuf autres modules conservent leur version issue de l'alpha.212. diff --git a/ambiance/build.gradle b/ambiance/build.gradle index 5b08cfc..d5ca96a 100644 --- a/ambiance/build.gradle +++ b/ambiance/build.gradle @@ -70,7 +70,7 @@ tasks.named("test") { tasks.register("verifyAmbiance") { group = "verification" - description = "Checks Ambiance alpha.16, including the Minecraft 26.2 white-disc event hook." + description = "Checks Ambiance alpha.17, including deferred white-disc playback startup." inputs.files(fileTree("src/main/java")) inputs.files(fileTree("src/main/resources")) inputs.file(rootProject.file("pack/migrations/26.2.0-alpha.125-realtime-calendar-to-alpha.126.json")) @@ -123,7 +123,7 @@ tasks.register("verifyAmbiance") { def levelEventMixin = file( "src/main/java/fr/koka99cab/sanctuary26/ambiance/mixin/client/LevelEventHandlerMixin.java").text - if (project.version.toString() != "0.0.0-alpha.16" + if (project.version.toString() != "0.0.0-alpha.17" || migration.source?.pack_version != "26.2.0-alpha.125" || migration.target?.pack_version != "26.2.0-alpha.126" || migration.target?.modules?.ambiance != "0.0.0-alpha.1" @@ -321,7 +321,9 @@ tasks.register("verifyAmbiance") { || !javaText.contains('PlayerLookup.tracking(level, pos)') || !javaText.contains('ServerPlayNetworking.canSend(receiver, WhiteDiscPlaybackPayload.ID)') || !javaText.contains('MAXIMUM_SIMULTANEOUS_DISCS = 4') - || !javaText.contains('REQUESTS.size() + PLAYBACKS.size() >= MAXIMUM_SIMULTANEOUS_DISCS') + || !javaText.contains('PENDING_STARTS.size() + REQUESTS.size() + PLAYBACKS.size()') + || !javaText.contains('MAXIMUM_START_WAIT_TICKS = 100') + || !javaText.contains('tickPendingStarts(client)') || !javaText.contains('client.level.dimension().equals(playback.dimension())') || !javaText.contains('MAXIMUM_TRACK_MILLIS = 30L * 60L * 1_000L') || !javaText.contains('new Pcm16AudioDataFormat(1, LavaplayerAudioStream.SAMPLE_RATE') @@ -347,7 +349,7 @@ tasks.register("verifyAmbiance") { } || !file("build.gradle").text.contains('dev.arbjerg:lavaplayer:2.2.7') || !file("build.gradle").text.contains('dev.lavalink.youtube:v2:1.18.2')) { - throw new GradleException("Ambiance alpha.16 white-disc playback contract is incomplete") + throw new GradleException("Ambiance alpha.17 white-disc playback contract is incomplete") } [ diff --git a/ambiance/src/main/java/fr/koka99cab/sanctuary26/ambiance/client/audio/WhiteDiscAudioPlayer.java b/ambiance/src/main/java/fr/koka99cab/sanctuary26/ambiance/client/audio/WhiteDiscAudioPlayer.java index 0001011..18feb44 100644 --- a/ambiance/src/main/java/fr/koka99cab/sanctuary26/ambiance/client/audio/WhiteDiscAudioPlayer.java +++ b/ambiance/src/main/java/fr/koka99cab/sanctuary26/ambiance/client/audio/WhiteDiscAudioPlayer.java @@ -30,10 +30,12 @@ import net.minecraft.world.phys.Vec3; /** Client-only, positional and resource-bounded YouTube playback for white discs. */ public final class WhiteDiscAudioPlayer { private static final int MAXIMUM_SIMULTANEOUS_DISCS = 4; + private static final int MAXIMUM_START_WAIT_TICKS = 100; private static final long MAXIMUM_TRACK_MILLIS = 30L * 60L * 1_000L; private static final long FINISHED_GRACE_NANOS = 5_000_000_000L; private static final float ATTENUATION_DISTANCE = 64.0F; private static final DefaultAudioPlayerManager MANAGER = createManager(); + private static final Map PENDING_STARTS = new HashMap<>(); private static final Map REQUESTS = new HashMap<>(); private static final LinkedHashMap PLAYBACKS = new LinkedHashMap<>(); private static long requestSequence; @@ -43,16 +45,27 @@ public final class WhiteDiscAudioPlayer { } public static void play(Minecraft client, BlockPos pos, String videoId) { - if (shutdown || !WhiteDiscUrl.validVideoId(videoId) || !validTarget(client, pos)) return; + if (shutdown || !WhiteDiscUrl.validVideoId(videoId) || !validJukeboxTarget(client, pos)) return; long key = pos.asLong(); - if (REQUESTS.containsKey(key)) return; + if (PENDING_STARTS.containsKey(key) || REQUESTS.containsKey(key)) return; boolean replacingPlayback = PLAYBACKS.containsKey(key); if (!replacingPlayback - && REQUESTS.size() + PLAYBACKS.size() >= MAXIMUM_SIMULTANEOUS_DISCS) return; + && PENDING_STARTS.size() + REQUESTS.size() + PLAYBACKS.size() + >= MAXIMUM_SIMULTANEOUS_DISCS) return; stop(key); + client.player.sendOverlayMessage(Component.translatable("message.ambiance.white_disc.loading")); + if (!validTarget(client, pos)) { + PENDING_STARTS.put(key, new PendingStart(pos.immutable(), client.level.dimension(), videoId, + MAXIMUM_START_WAIT_TICKS)); + return; + } + startLoading(client, pos, videoId); + } + + private static void startLoading(Minecraft client, BlockPos pos, String videoId) { + long key = pos.asLong(); long request = ++requestSequence; REQUESTS.put(key, request); - client.player.sendOverlayMessage(Component.translatable("message.ambiance.white_disc.loading")); MANAGER.loadItemOrdered(key, WhiteDiscUrl.canonicalUrl(videoId), new AudioLoadResultHandler() { @Override public void trackLoaded(AudioTrack track) { @@ -137,6 +150,7 @@ public final class WhiteDiscAudioPlayer { } private static void stop(long key) { + PENDING_STARTS.remove(key); REQUESTS.remove(key); Playback playback = PLAYBACKS.remove(key); if (playback == null) return; @@ -150,6 +164,7 @@ public final class WhiteDiscAudioPlayer { clear(); return; } + tickPendingStarts(client); long now = System.nanoTime(); for (Map.Entry entry : Map.copyOf(PLAYBACKS).entrySet()) { Playback playback = entry.getValue(); @@ -167,6 +182,7 @@ public final class WhiteDiscAudioPlayer { } public static void clear() { + PENDING_STARTS.clear(); REQUESTS.clear(); for (long key : PLAYBACKS.keySet().stream().mapToLong(Long::longValue).toArray()) stop(key); } @@ -189,11 +205,46 @@ public final class WhiteDiscAudioPlayer { } private static boolean validTarget(Minecraft client, BlockPos pos) { + return validJukeboxTarget(client, pos) + && client.level.getBlockState(pos).getValue(JukeboxBlock.HAS_RECORD); + } + + private static boolean validJukeboxTarget(Minecraft client, BlockPos pos) { return client.player != null && client.level != null && client.player.distanceToSqr(Vec3.atCenterOf(pos)) <= ATTENUATION_DISTANCE * ATTENUATION_DISTANCE * 4.0D - && client.level.getBlockState(pos).is(Blocks.JUKEBOX) - && client.level.getBlockState(pos).getValue(JukeboxBlock.HAS_RECORD); + && client.level.getBlockState(pos).is(Blocks.JUKEBOX); + } + + private static void tickPendingStarts(Minecraft client) { + for (Map.Entry entry : Map.copyOf(PENDING_STARTS).entrySet()) { + long key = entry.getKey(); + PendingStart pending = entry.getValue(); + if (!client.level.dimension().equals(pending.dimension()) + || !validJukeboxTarget(client, pending.pos())) { + PENDING_STARTS.remove(key); + continue; + } + if (validTarget(client, pending.pos())) { + PENDING_STARTS.remove(key); + startLoading(client, pending.pos(), pending.videoId()); + continue; + } + if (pending.ticksRemaining() <= 1) { + PENDING_STARTS.remove(key); + if (client.player != null) client.player.sendOverlayMessage( + Component.translatable("message.ambiance.white_disc.failed")); + continue; + } + PENDING_STARTS.put(key, pending.waitOneTick()); + } + } + + private record PendingStart(BlockPos pos, ResourceKey dimension, String videoId, + int ticksRemaining) { + private PendingStart waitOneTick() { + return new PendingStart(pos, dimension, videoId, ticksRemaining - 1); + } } private record Playback(BlockPos pos, ResourceKey dimension, diff --git a/ambiance/src/test/java/fr/koka99cab/sanctuary26/ambiance/white_disc/WhiteDiscUrlSmoke.java b/ambiance/src/test/java/fr/koka99cab/sanctuary26/ambiance/white_disc/WhiteDiscUrlSmoke.java index 821d55b..e81cd91 100644 --- a/ambiance/src/test/java/fr/koka99cab/sanctuary26/ambiance/white_disc/WhiteDiscUrlSmoke.java +++ b/ambiance/src/test/java/fr/koka99cab/sanctuary26/ambiance/white_disc/WhiteDiscUrlSmoke.java @@ -10,6 +10,8 @@ public final class WhiteDiscUrlSmoke { public static void main(String[] args) { require(VIDEO_ID.equals(id("https://www.youtube.com/watch?v=" + VIDEO_ID)), "watch URL rejected"); require(VIDEO_ID.equals(id("https://youtu.be/" + VIDEO_ID + "?si=abc")), "short URL rejected"); + require("Ue5ZBe-GzSM".equals(id("https://youtu.be/Ue5ZBe-GzSM?si=78LSbtBQzUYzhA2Y")), + "reported white-disc URL rejected"); require(VIDEO_ID.equals(id("https://music.youtube.com/watch?v=" + VIDEO_ID)), "music URL rejected"); require(VIDEO_ID.equals(id("https://www.youtube.com/shorts/" + VIDEO_ID)), "shorts URL rejected"); require(VIDEO_ID.equals(id("https://www.youtube.com/embed/" + VIDEO_ID)), "embed URL rejected"); diff --git a/build.gradle b/build.gradle index aac6c21..55ade92 100644 --- a/build.gradle +++ b/build.gradle @@ -4548,7 +4548,7 @@ tasks.register("verifyWeatherTntAmbianceRelease") { def ouchManifest = new JsonSlurper().parse(file("ouch/src/main/resources/fabric.mod.json")) def rootBuild = file("build.gradle").text if (rootProject.pack_version != "26.2.0-alpha.213" - || rootProject.ambiance_version != "0.0.0-alpha.16" + || rootProject.ambiance_version != "0.0.0-alpha.17" || rootProject.ouch_version != "0.0.0-alpha.10" || migration.source?.pack_version != "26.2.0-alpha.199" || migration.source?.modules != [ambiance: "0.0.0-alpha.11", ouch: "0.0.0-alpha.8"] @@ -5319,7 +5319,7 @@ tasks.register("verifyCanapliaFoundation") { "ambiance/src/main/java/fr/koka99cab/sanctuary26/ambiance/api/visual/AmbianceVisualEffectApi.java").text if (rootProject.pack_version != "26.2.0-alpha.213" || rootProject.itsalive_version != "0.0.0-alpha.36" - || rootProject.ambiance_version != "0.0.0-alpha.16" + || rootProject.ambiance_version != "0.0.0-alpha.17" || migration.issue != 35 || migration.source?.fabric_id != "canaplia" || migration.source?.version != "2.0.0" @@ -5570,7 +5570,7 @@ tasks.register("verifyCanapliaAlpha209Release") { itsalive: "0.0.0-alpha.33", onlyfun: "0.0.0-alpha.20"] if (rootProject.pack_version != "26.2.0-alpha.213" - || rootProject.ambiance_version != "0.0.0-alpha.16" + || rootProject.ambiance_version != "0.0.0-alpha.17" || rootProject.itsalive_version != "0.0.0-alpha.36" || rootProject.onlyfun_version != "0.0.0-alpha.24" || migration.source?.pack_version != "26.2.0-alpha.207" @@ -5785,7 +5785,7 @@ tasks.register("verifyAlpha211Release") { def currentModules = expectedTarget + [ sanctuary: "0.0.0-alpha.120", ambiance: "0.0.0-alpha.16"] - def workspaceModules = currentModules + [ambiance: "0.0.0-alpha.16"] + def workspaceModules = currentModules + [ambiance: "0.0.0-alpha.17"] def expectedDataVersions = [ambiance: 2, itsalive: 3, onlyfun: 2, ouch: 1, sanctuary: 11] def expectedProtocols = [ambiance: 6, itsalive: 4, onlyfun: 3, ouch: 1, sanctuary: 22] def manifests = expectedTarget.collectEntries { id, version -> @@ -5948,7 +5948,7 @@ tasks.register("verifyAlpha213Release") { it.path == "mods/jei-26.2-fabric-30.28.0.193.jar" } if (rootProject.pack_version != "26.2.0-alpha.213" - || rootProject.ambiance_version != "0.0.0-alpha.16" + || rootProject.ambiance_version != "0.0.0-alpha.17" || migration.source?.pack_version != "26.2.0-alpha.212" || migration.source?.modules != [ambiance: "0.0.0-alpha.15"] || migration.target?.pack_version != "26.2.0-alpha.213" @@ -5980,7 +5980,6 @@ tasks.register("verifyAlpha213Release") { || !packwizIndex.contains('file = "mods/ambiance-0.0.0-alpha.16.jar"') || packwizIndex.contains('file = "mods/ambiance-0.0.0-alpha.15.jar"') || !changelog.contains("## `26.2.0-alpha.213`") - || changelog.contains("## Modifications non distribuées") || !readme.contains("Version courante du pack distribué : **`26.2.0-alpha.213`**")) { throw new GradleException("The alpha.213 Ambiance startup-fix release is incomplete") } diff --git a/gradle.properties b/gradle.properties index 17830f7..5df3b37 100644 --- a/gradle.properties +++ b/gradle.properties @@ -12,7 +12,7 @@ fabric_api_version=0.158.0+26.2 # Standby modules stay at zero until their migration really begins. mod_version=0.0.0-alpha.0 module_lifecycle=standby -ambiance_version=0.0.0-alpha.16 +ambiance_version=0.0.0-alpha.17 ambiance_lifecycle=active redstoner_version=0.0.0-alpha.10 redstoner_lifecycle=active