nosingleplayer 1.1.6 : fond du menu titre enfin affiché

Trois bugs cumulés, tous vérifiés contre le jar client 26.3 :

1. blit() : l'overload 11 args réutilise w/h comme région source
   (u0 = u/tw, u1 = (u+w)/tw) -> on n'affichait que le coin haut-gauche
   de l'image (noir). On utilise l'overload 13 args avec une région
   source explicite + recadrage "cover" (pas de déformation).

2. Texture introuvable : en MC 26.3 le ResourceManager ne lit plus les
   packs depuis les jars du classpath (seulement des dossiers), donc les
   assets d'un mod ne sont plus exposés -> placeholder magenta.
   Vérifié : "Reloading ResourceManager: vanilla, vanilla" et
   namespaces=[minecraft, realms]. Le PNG est donc lu dans le jar du
   mod (getResourceAsStream) et uploadé en DynamicTexture, enregistrée
   dans le TextureManager.

3. Filtrage : sampler NEAREST par défaut (aliasing visible) -> sampler
   linéaire + clamp-to-edge.

Asset : PNG RGBA 3840x2400.
This commit is contained in:
Anaïs Saraiva
2026-10-19 21:23:41 +02:00
parent 8053411565
commit fc80845f5c
3 changed files with 94 additions and 6 deletions
+1 -1
View File
@@ -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
@@ -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<String> name, NativeImage image) {
super(name, image);
this.sampler = RenderSystem.getSamplerCache().getClampToEdge(FilterMode.LINEAR);
}
}
// Pas de logo Minecraft.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

After

Width:  |  Height:  |  Size: 1.9 MiB