diff --git a/mods/nosingleplayer/gradle.properties b/mods/nosingleplayer/gradle.properties index 3b41758..cb1c452 100644 --- a/mods/nosingleplayer/gradle.properties +++ b/mods/nosingleplayer/gradle.properties @@ -5,7 +5,7 @@ loader_version=0.20.0 loom_version=1.19.1 # Mod Properties -mod_version=1.1.5 +mod_version=1.1.6 maven_group=fr.surviepote archives_base_name=nosingleplayer diff --git a/mods/nosingleplayer/src/main/java/fr/surviepote/nosingleplayer/mixin/TitleScreenRenderMixin.java b/mods/nosingleplayer/src/main/java/fr/surviepote/nosingleplayer/mixin/TitleScreenRenderMixin.java index edaf3a0..d731eeb 100644 --- a/mods/nosingleplayer/src/main/java/fr/surviepote/nosingleplayer/mixin/TitleScreenRenderMixin.java +++ b/mods/nosingleplayer/src/main/java/fr/surviepote/nosingleplayer/mixin/TitleScreenRenderMixin.java @@ -1,17 +1,26 @@ package fr.surviepote.nosingleplayer.mixin; +import com.mojang.blaze3d.platform.NativeImage; +import com.mojang.blaze3d.systems.RenderSystem; import com.mojang.realmsclient.gui.screens.RealmsNotificationsScreen; +import com.mojang.renderpearl.api.textures.FilterMode; +import fr.surviepote.nosingleplayer.NoSingleplayer; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiGraphicsExtractor; import net.minecraft.client.gui.components.LogoRenderer; import net.minecraft.client.gui.screens.TitleScreen; import net.minecraft.client.renderer.RenderPipelines; +import net.minecraft.client.renderer.texture.DynamicTexture; import net.minecraft.resources.Identifier; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Redirect; +import java.io.IOException; +import java.io.InputStream; +import java.util.function.Supplier; + @Mixin(TitleScreen.class) public abstract class TitleScreenRenderMixin { @@ -19,23 +28,102 @@ public abstract class TitleScreenRenderMixin { private static final Identifier SURVIEPOTE_BACKGROUND = Identifier.fromNamespaceAndPath( "nosingleplayer", "textures/gui/background.png"); - // Taille réelle de background.png (fond plein écran étiré). + // Le PNG est lu directement dans le jar du mod (voir surviepote$loadBackground). @Unique - private static final int BACKGROUND_TEX_W = 3840; + private static final String SURVIEPOTE_BACKGROUND_RESOURCE = + "/assets/nosingleplayer/textures/gui/background.png"; + @Unique - private static final int BACKGROUND_TEX_H = 2400; + private static DynamicTexture surviepote$background; + + @Unique + private static int surviepote$backgroundWidth; + + @Unique + private static int surviepote$backgroundHeight; // Pas de panorama dynamique : fond statique custom à la place. // Note : extractPanorama est déclarée dans Screen (héritée), on redirige donc // l'APPEL dans TitleScreen plutôt que la méthode elle-même. + // Le fond couvre l'écran sans déformation : recadrage centré type "cover". @Redirect(method = "extractRenderState", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screens/TitleScreen;extractPanorama(Lnet/minecraft/client/gui/GuiGraphicsExtractor;F)V")) private void surviepote$customBackground(TitleScreen self, GuiGraphicsExtractor extractor, float partialTick) { int w = Minecraft.getInstance().getWindow().getGuiScaledWidth(); int h = Minecraft.getInstance().getWindow().getGuiScaledHeight(); - extractor.blit(RenderPipelines.GUI_TEXTURED, SURVIEPOTE_BACKGROUND, 0, 0, 0.0F, 0.0F, - w, h, BACKGROUND_TEX_W, BACKGROUND_TEX_H, -1); + + surviepote$loadBackground(); + if (surviepote$background == null) { + return; + } + + float screenAspect = (float) w / (float) h; + float texAspect = (float) surviepote$backgroundWidth / (float) surviepote$backgroundHeight; + + int u, v, srcW, srcH; + if (screenAspect > texAspect) { + // Écran plus large que l'image : on rogne en haut/bas. + srcW = surviepote$backgroundWidth; + srcH = Math.round(surviepote$backgroundWidth / screenAspect); + u = 0; + v = (surviepote$backgroundHeight - srcH) / 2; + } else { + // Écran plus haut que l'image : on rogne à gauche/droite. + srcH = surviepote$backgroundHeight; + srcW = Math.round(surviepote$backgroundHeight * screenAspect); + u = (surviepote$backgroundWidth - srcW) / 2; + v = 0; + } + + // Surcharge complète : (u, v) = origine dans la texture, (w, h) = taille à + // l'écran, (srcW, srcH) = taille de la région source dans la texture. + // L'overload court réutilise w/h comme région source (=> zoom sur un coin). + extractor.blit(RenderPipelines.GUI_TEXTURED, SURVIEPOTE_BACKGROUND, + 0, 0, (float) u, (float) v, + w, h, srcW, srcH, + surviepote$backgroundWidth, surviepote$backgroundHeight, -1); + } + + /** + * MC 26.3 n'expose plus les ressources des mods au ResourceManager : les packs + * ne sont lus que depuis des dossiers, pas depuis les jars du classpath. Une + * texture référencée par son Identifier est donc introuvable (placeholder + * magenta). On charge donc le PNG depuis le jar du mod et on enregistre une + * DynamicTexture dans le TextureManager. + */ + @Unique + private static void surviepote$loadBackground() { + var textures = Minecraft.getInstance().getTextureManager(); + if (surviepote$background != null + && textures.getTexture(SURVIEPOTE_BACKGROUND) == surviepote$background) { + return; // déjà chargé et toujours enregistré + } + try (InputStream in = NoSingleplayer.class.getResourceAsStream(SURVIEPOTE_BACKGROUND_RESOURCE)) { + if (in == null) { + NoSingleplayer.LOGGER.error("[SurviePote] Fond introuvable dans le jar : {}", + SURVIEPOTE_BACKGROUND_RESOURCE); + return; + } + NativeImage image = NativeImage.read(in); + surviepote$backgroundWidth = image.getWidth(); + surviepote$backgroundHeight = image.getHeight(); + surviepote$background = new LinearDynamicTexture(() -> "SurviePote Background", image); + textures.register(SURVIEPOTE_BACKGROUND, surviepote$background); + NoSingleplayer.LOGGER.info("[SurviePote] Fond chargé : {}x{}", + surviepote$backgroundWidth, surviepote$backgroundHeight); + } catch (IOException e) { + NoSingleplayer.LOGGER.error("[SurviePote] Échec de chargement du fond", e); + } + } + + /** DynamicTexture avec filtrage linéaire : le fond est réduit à la taille de l'écran. */ + @Unique + private static final class LinearDynamicTexture extends DynamicTexture { + LinearDynamicTexture(Supplier name, NativeImage image) { + super(name, image); + this.sampler = RenderSystem.getSamplerCache().getClampToEdge(FilterMode.LINEAR); + } } // Pas de logo Minecraft. diff --git a/mods/nosingleplayer/src/main/resources/assets/nosingleplayer/textures/gui/background.png b/mods/nosingleplayer/src/main/resources/assets/nosingleplayer/textures/gui/background.png index ebf4bb1..1c6a65e 100644 Binary files a/mods/nosingleplayer/src/main/resources/assets/nosingleplayer/textures/gui/background.png and b/mods/nosingleplayer/src/main/resources/assets/nosingleplayer/textures/gui/background.png differ