Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
00e9ff003c | ||
|
|
e5b2fc5a79 | ||
|
|
2bb5b30857 | ||
|
|
4fd8aa09b6 | ||
|
|
177a765783 | ||
|
|
4c7da4cab5 | ||
|
|
1ff5f52ce8 |
@@ -4,8 +4,8 @@ Type=Application
|
|||||||
Name=SurviePote
|
Name=SurviePote
|
||||||
GenericName=Minecraft Launcher
|
GenericName=Minecraft Launcher
|
||||||
Comment=Launcher officiel SurviePote — rejoins mcpv.ihorizon.org
|
Comment=Launcher officiel SurviePote — rejoins mcpv.ihorizon.org
|
||||||
TryExec=/home/kisakay/Applications/SurviePote-setup-1.0.0.AppImage
|
TryExec=/home/kisakay/Applications/SurviePote-setup-1.1.1.AppImage
|
||||||
Exec=/home/kisakay/Applications/SurviePote-setup-1.0.0.AppImage --no-sandbox %U
|
Exec=/home/kisakay/Applications/SurviePote-setup-1.1.1.AppImage --no-sandbox %U
|
||||||
Icon=surviepote
|
Icon=surviepote
|
||||||
Terminal=false
|
Terminal=false
|
||||||
Categories=Game;
|
Categories=Game;
|
||||||
|
|||||||
@@ -487,13 +487,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "fr.surviepote:nosingleplayer:1.1.8@jar",
|
"id": "fr.surviepote:nosingleplayer:1.2.0@jar",
|
||||||
"name": "NoSingleplayer (SurviePote)",
|
"name": "NoSingleplayer (SurviePote)",
|
||||||
"type": "FabricMod",
|
"type": "FabricMod",
|
||||||
"artifact": {
|
"artifact": {
|
||||||
"size": 3637392,
|
"size": 870981,
|
||||||
"url": "https://git.kisakay.com/k/mc/raw/branch/main/atlas/root/servers/SurviePote-26.3/fabricmods/required/nosingleplayer-1.1.8.jar",
|
"url": "https://git.kisakay.com/k/mc/raw/branch/main/atlas/root/servers/SurviePote-26.3/fabricmods/optionalon/nosingleplayer-1.2.0.jar",
|
||||||
"MD5": "dc69cd7ebc5d10ba4797c4e212e47e0c"
|
"MD5": "107e3b749568d916e556479efbbda3c6"
|
||||||
|
},
|
||||||
|
"required": {
|
||||||
|
"value": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -612,16 +615,6 @@
|
|||||||
"value": false
|
"value": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"id": "generated.fabricmod:spatial:gui-1.8+26.3-fabric@jar",
|
|
||||||
"name": "Spatial",
|
|
||||||
"type": "FabricMod",
|
|
||||||
"artifact": {
|
|
||||||
"size": 276562,
|
|
||||||
"url": "https://git.kisakay.com/k/mc/raw/branch/main/atlas/root/servers/SurviePote-26.3/fabricmods/required/spatial-gui-1.8+26.3-fabric.jar",
|
|
||||||
"MD5": "80fcb9b1ed083b8515fa78e6a5cd86a4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"id": "com.jaquadro.minecraft:storagedrawers:26.3.0.1@jar",
|
"id": "com.jaquadro.minecraft:storagedrawers:26.3.0.1@jar",
|
||||||
"name": "Storage Drawers",
|
"name": "Storage Drawers",
|
||||||
|
|||||||
@@ -185,6 +185,10 @@ class ProcessBuilder {
|
|||||||
const child = child_process.spawn(ConfigManager.getJavaExecutable(this.server.rawServer.id), args, {
|
const child = child_process.spawn(ConfigManager.getJavaExecutable(this.server.rawServer.id), args, {
|
||||||
cwd: this.gameDir,
|
cwd: this.gameDir,
|
||||||
detached: ConfigManager.getLaunchDetached(),
|
detached: ConfigManager.getLaunchDetached(),
|
||||||
|
// Windows : sans ça, java.exe ouvre sa propre console (detached => nouvelle
|
||||||
|
// console) : une fenêtre noire, vide (stdout est pipe vers le launcher),
|
||||||
|
// qui traîne par-dessus la fenêtre du jeu pendant toute la session.
|
||||||
|
windowsHide: true,
|
||||||
env: gameEnv
|
env: gameEnv
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "surviepote-launcher",
|
"name": "surviepote-launcher",
|
||||||
"version": "1.1.0",
|
"version": "1.1.1",
|
||||||
"productName": "SurviePote",
|
"productName": "SurviePote",
|
||||||
"description": "Launcher officiel SurviePote - Rejoins mcpv.ihorizon.org",
|
"description": "Launcher officiel SurviePote - Rejoins mcpv.ihorizon.org",
|
||||||
"author": "SurviePote",
|
"author": "SurviePote",
|
||||||
|
|||||||
@@ -13,4 +13,27 @@ au serveur unique :
|
|||||||
pas de refmap nécessaire.
|
pas de refmap nécessaire.
|
||||||
|
|
||||||
Build : `cd mods/nosingleplayer && ./gradlew build` (dans nix-shell racine pour Java 25).
|
Build : `cd mods/nosingleplayer && ./gradlew build` (dans nix-shell racine pour Java 25).
|
||||||
Jar : `build/libs/nosingleplayer-1.1.4.jar` → copié dans `atlas/.../fabricmods/required/`.
|
Jar : `build/libs/nosingleplayer-1.2.0.jar` → copié dans `atlas/.../fabricmods/optionalon/`.
|
||||||
|
|
||||||
|
## Textures
|
||||||
|
|
||||||
|
MC 26.3 n'expose pas les ressources des mods au ResourceManager (vérifié : le panorama
|
||||||
|
vanilla affiche le stock Mojang, pas les `panorama_*.png` du mod). Les PNG sont donc
|
||||||
|
lus **dans le jar** et enregistrés via le mécanisme officiel de MC
|
||||||
|
(`ReloadableTexture` + `TextureContents`) : c'est MC qui crée la texture GPU et le
|
||||||
|
sampler, exactement comme pour ses propres textures.
|
||||||
|
|
||||||
|
⚠️ La texture GPU doit être créée **hors de l'extraction du rendu** (ici dans
|
||||||
|
`TitleScreen.init`) : la créer pendant l'enregistrement des commandes d'une frame
|
||||||
|
invalide le frame entier sur les backends à command encoder (Vulkan) → menu titre
|
||||||
|
**entièrement noir** (constaté sur Windows + NVIDIA).
|
||||||
|
|
||||||
|
## Diagnostic
|
||||||
|
|
||||||
|
- `-Dsurviepote.customTitle=false` (options JVM du launcher) : repasser au menu **vanilla**
|
||||||
|
(panorama vanilla au lieu du fond custom). Sert à isoler un souci de rendu du fond custom.
|
||||||
|
- Le mod log au démarrage du menu : `[SurviePote] Texture enregistrée : … (1920x1200)` et
|
||||||
|
`[SurviePote] GPU: <nom> | <vendor> | pilote <driver> | backend <OpenGL|Vulkan>`.
|
||||||
|
- Si le fond custom échoue (texture refusée par le GPU, jar corrompu…), le mod se repli
|
||||||
|
automatiquement sur le panorama vanilla au lieu de laisser un écran noir.
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ loader_version=0.20.0
|
|||||||
loom_version=1.19.1
|
loom_version=1.19.1
|
||||||
|
|
||||||
# Mod Properties
|
# Mod Properties
|
||||||
mod_version=1.1.8
|
mod_version=1.2.0
|
||||||
maven_group=fr.surviepote
|
maven_group=fr.surviepote
|
||||||
archives_base_name=nosingleplayer
|
archives_base_name=nosingleplayer
|
||||||
|
|
||||||
|
|||||||
@@ -1,69 +1,74 @@
|
|||||||
package fr.surviepote.nosingleplayer;
|
package fr.surviepote.nosingleplayer;
|
||||||
|
|
||||||
import com.mojang.blaze3d.platform.NativeImage;
|
|
||||||
import com.mojang.blaze3d.systems.RenderSystem;
|
|
||||||
import com.mojang.renderpearl.api.textures.FilterMode;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import net.minecraft.client.Minecraft;
|
import net.minecraft.client.Minecraft;
|
||||||
import net.minecraft.client.renderer.texture.DynamicTexture;
|
import net.minecraft.client.renderer.texture.TextureContents;
|
||||||
import net.minecraft.resources.Identifier;
|
import net.minecraft.resources.Identifier;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Chargement des textures du mod.
|
* Enregistrement des textures du mod dans le TextureManager de MC.
|
||||||
*
|
*
|
||||||
* <p>MC 26.3 n'expose plus les ressources des mods au ResourceManager : les packs ne
|
* <p>IMPORTANT : l'enregistrement doit être fait sur le thread de rendu mais
|
||||||
* sont lus que depuis des dossiers, pas depuis les jars du classpath. Une texture
|
* <b>en dehors</b> de l'extraction du rendu (ex. dans {@code TitleScreen.init}).
|
||||||
* référencée par son Identifier est donc introuvable (placeholder magenta). On lit
|
* Créer une ressource GPU pendant l'enregistrement des commandes d'une frame
|
||||||
* donc le PNG directement dans le jar du mod et on enregistre une DynamicTexture dans
|
* invalide le frame entier sur les backends à command encoder (Vulkan/NVIDIA) :
|
||||||
* le TextureManager.
|
* résultat, un menu titre entièrement noir. D'où le try/catch + le repli sur le
|
||||||
|
* panorama vanilla dans TitleScreenRenderMixin.
|
||||||
*/
|
*/
|
||||||
public final class ModTextures {
|
public final class ModTextures {
|
||||||
|
|
||||||
private static final Map<Identifier, DynamicTexture> CACHE = new HashMap<>();
|
private static final Map<Identifier, SurviePoteTexture> CACHE = new HashMap<>();
|
||||||
|
|
||||||
private ModTextures() {
|
private ModTextures() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renvoie la texture (chargée depuis le jar du mod), ou null si le fichier est absent.
|
* Enregistre une texture lue dans le jar du mod.
|
||||||
*
|
*
|
||||||
* @param id identifiant sous lequel la texture est enregistrée
|
* @param id identifiant sous lequel la texture est enregistrée
|
||||||
* @param resourcePath chemin dans le jar, ex {@code /assets/nosingleplayer/...}
|
* @param resourcePath chemin dans le jar, ex {@code /assets/nosingleplayer/...}
|
||||||
* @param name nom affiché dans les crash reports GPU
|
* @param blur true = filtrage linéaire, false = nearest (pixel art)
|
||||||
* @param linear true = filtrage linéaire (images agrandies), false = nearest (pixel art)
|
* @return true si la texture est utilisable
|
||||||
*/
|
*/
|
||||||
public static DynamicTexture get(Identifier id, String resourcePath, String name, boolean linear) {
|
public static boolean register(Identifier id, String resourcePath, boolean blur) {
|
||||||
DynamicTexture texture = CACHE.get(id);
|
Minecraft minecraft = Minecraft.getInstance();
|
||||||
var textures = Minecraft.getInstance().getTextureManager();
|
SurviePoteTexture cached = CACHE.get(id);
|
||||||
if (texture != null && textures.getTexture(id) == texture) {
|
if (cached != null && minecraft.getTextureManager().getTexture(id) == cached) {
|
||||||
return texture; // déjà chargée et toujours enregistrée
|
return true; // déjà enregistrée et toujours en place
|
||||||
}
|
}
|
||||||
try (InputStream in = ModTextures.class.getResourceAsStream(resourcePath)) {
|
try {
|
||||||
if (in == null) {
|
SurviePoteTexture texture = new SurviePoteTexture(id, resourcePath, blur);
|
||||||
NoSingleplayer.LOGGER.error("[SurviePote] Texture introuvable dans le jar : {}", resourcePath);
|
TextureContents contents = texture.loadContents(minecraft.getResourceManager());
|
||||||
return null;
|
texture.apply(contents);
|
||||||
}
|
minecraft.getTextureManager().register(id, texture);
|
||||||
NativeImage image = NativeImage.read(in);
|
|
||||||
texture = linear
|
|
||||||
? new LinearTexture(() -> name, image)
|
|
||||||
: new DynamicTexture(() -> name, image);
|
|
||||||
CACHE.put(id, texture);
|
CACHE.put(id, texture);
|
||||||
textures.register(id, texture);
|
NoSingleplayer.LOGGER.info("[SurviePote] Texture enregistrée : {} ({}x{})",
|
||||||
return texture;
|
id, texture.getWidth(), texture.getHeight());
|
||||||
} catch (IOException e) {
|
return true;
|
||||||
NoSingleplayer.LOGGER.error("[SurviePote] Échec de chargement de la texture " + resourcePath, e);
|
} catch (Throwable t) {
|
||||||
return null;
|
NoSingleplayer.LOGGER.error("[SurviePote] Échec d'enregistrement de la texture " + resourcePath, t);
|
||||||
|
CACHE.remove(id);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** DynamicTexture avec filtrage linéaire (utile quand l'image est réduite à l'écran). */
|
/** True si la texture est enregistrée ET encore présente dans le TextureManager. */
|
||||||
private static final class LinearTexture extends DynamicTexture {
|
public static boolean isRegistered(Identifier id) {
|
||||||
LinearTexture(java.util.function.Supplier<String> name, NativeImage image) {
|
SurviePoteTexture cached = CACHE.get(id);
|
||||||
super(name, image);
|
return cached != null
|
||||||
this.sampler = RenderSystem.getSamplerCache().getClampToEdge(FilterMode.LINEAR);
|
&& Minecraft.getInstance().getTextureManager().getTexture(id) == cached;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Largeur de la texture enregistrée (0 si inconnue). */
|
||||||
|
public static int getWidth(Identifier id) {
|
||||||
|
SurviePoteTexture cached = CACHE.get(id);
|
||||||
|
return cached != null ? cached.getWidth() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Hauteur de la texture enregistrée (0 si inconnue). */
|
||||||
|
public static int getHeight(Identifier id) {
|
||||||
|
SurviePoteTexture cached = CACHE.get(id);
|
||||||
|
return cached != null ? cached.getHeight() : 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
package fr.surviepote.nosingleplayer;
|
||||||
|
|
||||||
|
import com.mojang.blaze3d.platform.NativeImage;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import net.minecraft.client.renderer.texture.MipmapStrategy;
|
||||||
|
import net.minecraft.client.renderer.texture.ReloadableTexture;
|
||||||
|
import net.minecraft.client.renderer.texture.TextureContents;
|
||||||
|
import net.minecraft.client.resources.metadata.texture.TextureMetadataSection;
|
||||||
|
import net.minecraft.resources.Identifier;
|
||||||
|
import net.minecraft.server.packs.resources.ResourceManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Texture du mod (PNG lu dans le jar) gérée exactement comme une texture vanilla.
|
||||||
|
*
|
||||||
|
* <p>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 lit
|
||||||
|
* donc le PNG directement dans le jar du mod et on l'enregistre via le mécanisme
|
||||||
|
* officiel {@link ReloadableTexture} : c'est MC qui crée la texture GPU ET le
|
||||||
|
* sampler, exactement comme pour ses propres textures. Aucun sampler custom, aucun
|
||||||
|
* upload manuel => compatible avec tous les backends (OpenGL, Vulkan) et tous les
|
||||||
|
* pilotes (Mesa, NVIDIA, AMD, Intel).
|
||||||
|
*/
|
||||||
|
public class SurviePoteTexture extends ReloadableTexture {
|
||||||
|
|
||||||
|
private final String resourcePath;
|
||||||
|
private final boolean blur;
|
||||||
|
|
||||||
|
private int width;
|
||||||
|
private int height;
|
||||||
|
|
||||||
|
public SurviePoteTexture(Identifier id, String resourcePath, boolean blur) {
|
||||||
|
super(id);
|
||||||
|
this.resourcePath = resourcePath;
|
||||||
|
this.blur = blur;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TextureContents loadContents(ResourceManager resourceManager) throws IOException {
|
||||||
|
try (InputStream in = SurviePoteTexture.class.getResourceAsStream(this.resourcePath)) {
|
||||||
|
if (in == null) {
|
||||||
|
throw new IOException("Texture introuvable dans le jar : " + this.resourcePath);
|
||||||
|
}
|
||||||
|
NativeImage image = NativeImage.read(in);
|
||||||
|
this.width = image.getWidth();
|
||||||
|
this.height = image.getHeight();
|
||||||
|
// blur=true => filtrage linéaire (image agrandie/réduite),
|
||||||
|
// clamp=true => pas de répétition sur les bords.
|
||||||
|
return new TextureContents(image,
|
||||||
|
new TextureMetadataSection(this.blur, true, MipmapStrategy.AUTO, 0.0f));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getWidth() {
|
||||||
|
return this.width;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getHeight() {
|
||||||
|
return this.height;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -33,7 +33,7 @@ public class DiscordButton extends AbstractButton {
|
|||||||
super(x, y, width, height, Component.literal(label));
|
super(x, y, width, height, Component.literal(label));
|
||||||
this.action = action;
|
this.action = action;
|
||||||
// La texture doit être enregistrée avant le premier rendu.
|
// La texture doit être enregistrée avant le premier rendu.
|
||||||
ModTextures.get(TEXTURE, TEXTURE_PATH, "SurviePote Discord", false);
|
ModTextures.register(TEXTURE, TEXTURE_PATH, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ public class IconButton extends AbstractButton {
|
|||||||
this.texture = texture;
|
this.texture = texture;
|
||||||
this.action = action;
|
this.action = action;
|
||||||
// La texture doit être enregistrée avant le premier rendu.
|
// La texture doit être enregistrée avant le premier rendu.
|
||||||
ModTextures.get(texture, resourcePath, message.getString(), false);
|
ModTextures.register(texture, resourcePath, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -26,6 +26,14 @@ public class ServerStatusWidget extends AbstractWidget {
|
|||||||
private static final long REFRESH_MS = 15000L;
|
private static final long REFRESH_MS = 15000L;
|
||||||
private static final long TIMEOUT_MS = 8000L;
|
private static final long TIMEOUT_MS = 8000L;
|
||||||
|
|
||||||
|
/** Thread dédié : le ping ne doit jamais bloquer le thread de rendu. */
|
||||||
|
private static final java.util.concurrent.ExecutorService PING_EXECUTOR =
|
||||||
|
java.util.concurrent.Executors.newSingleThreadExecutor(r -> {
|
||||||
|
Thread t = new Thread(r, "SurviePote-ServerStatus");
|
||||||
|
t.setDaemon(true);
|
||||||
|
return t;
|
||||||
|
});
|
||||||
|
|
||||||
private static final int BG_COLOR = 0xB0000000;
|
private static final int BG_COLOR = 0xB0000000;
|
||||||
private static final int ONLINE_COLOR = 0xFF4CD964;
|
private static final int ONLINE_COLOR = 0xFF4CD964;
|
||||||
private static final int OFFLINE_COLOR = 0xFFE04B4B;
|
private static final int OFFLINE_COLOR = 0xFFE04B4B;
|
||||||
@@ -106,9 +114,15 @@ public class ServerStatusWidget extends AbstractWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void refresh() {
|
private void refresh() {
|
||||||
|
// ATTENTION : pingServer() fait une résolution DNS + ouvre une connexion.
|
||||||
|
// Appelé depuis extractWidgetRenderState (thread de rendu), ça BLOQUE le
|
||||||
|
// rendu du menu (écran figé/noir) le temps du timeout DNS. On le lance
|
||||||
|
// donc sur un thread dédié ; l'état lu par le rendu est "volatile".
|
||||||
try {
|
try {
|
||||||
this.pingStartedAt = System.currentTimeMillis();
|
this.pingStartedAt = System.currentTimeMillis();
|
||||||
this.pending = true;
|
this.pending = true;
|
||||||
|
PING_EXECUTOR.execute(() -> {
|
||||||
|
try {
|
||||||
this.pinger.pingServer(this.data, () -> {
|
this.pinger.pingServer(this.data, () -> {
|
||||||
}, () -> {
|
}, () -> {
|
||||||
}, EventLoopGroupHolder.remote(Minecraft.getInstance().options.useNativeTransport()));
|
}, EventLoopGroupHolder.remote(Minecraft.getInstance().options.useNativeTransport()));
|
||||||
@@ -118,6 +132,13 @@ public class ServerStatusWidget extends AbstractWidget {
|
|||||||
this.resolved = true;
|
this.resolved = true;
|
||||||
this.online = false;
|
this.online = false;
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
} catch (Exception e) {
|
||||||
|
NoSingleplayer.LOGGER.warn("[SurviePote] Ping serveur impossible : {}", e.toString());
|
||||||
|
this.pending = false;
|
||||||
|
this.resolved = true;
|
||||||
|
this.online = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,14 +1,24 @@
|
|||||||
package fr.surviepote.nosingleplayer.mixin;
|
package fr.surviepote.nosingleplayer.mixin;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import net.minecraft.client.gui.GuiGraphicsExtractor;
|
||||||
import net.minecraft.client.gui.components.Renderable;
|
import net.minecraft.client.gui.components.Renderable;
|
||||||
import net.minecraft.client.gui.screens.Screen;
|
import net.minecraft.client.gui.screens.Screen;
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||||
|
import org.spongepowered.asm.mixin.gen.Invoker;
|
||||||
|
|
||||||
@Mixin(Screen.class)
|
@Mixin(Screen.class)
|
||||||
public interface ScreenAccessor {
|
public interface ScreenAccessor {
|
||||||
|
|
||||||
@Accessor("renderables")
|
@Accessor("renderables")
|
||||||
List<Renderable> surviepote$getRenderables();
|
List<Renderable> surviepote$getRenderables();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Panorama vanilla (déclaré dans Screen, appelé par TitleScreen).
|
||||||
|
* Utilisé en repli quand le fond custom de SurviePote n'a pas pu être chargé :
|
||||||
|
* sans ça, la redirection de l'appel laisse l'écran entièrement noir.
|
||||||
|
*/
|
||||||
|
@Invoker("extractPanorama")
|
||||||
|
void surviepote$extractVanillaPanorama(GuiGraphicsExtractor extractor, float partialTick);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,7 +45,18 @@ public abstract class TitleScreenMixin {
|
|||||||
|
|
||||||
@Inject(method = "init", at = @At("TAIL"))
|
@Inject(method = "init", at = @At("TAIL"))
|
||||||
private void surviepote$customizeMenu(CallbackInfo ci) {
|
private void surviepote$customizeMenu(CallbackInfo ci) {
|
||||||
TitleScreen self = (TitleScreen) (Object) this;
|
// Garde-fou : si la personnalisation échoue, on NE DOIT PAS avoir retiré les
|
||||||
|
// boutons vanilla (menu vide + fond noir = "écran cassé"). Les retraits ne
|
||||||
|
// sont appliqués qu'après la création des widgets, en fin de méthode.
|
||||||
|
try {
|
||||||
|
surviepote$buildMenu((TitleScreen) (Object) this);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
NoSingleplayer.LOGGER.error("[SurviePote] Personnalisation du menu échouée, menu vanilla conservé.", t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Unique
|
||||||
|
private void surviepote$buildMenu(TitleScreen self) {
|
||||||
Minecraft minecraft = Minecraft.getInstance();
|
Minecraft minecraft = Minecraft.getInstance();
|
||||||
|
|
||||||
String solo = Component.translatable("menu.singleplayer").getString();
|
String solo = Component.translatable("menu.singleplayer").getString();
|
||||||
@@ -97,9 +108,9 @@ public abstract class TitleScreenMixin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (GuiEventListener widget : toRemove) {
|
// ATTENTION : les retraits des widgets vanilla ne sont appliqués qu'APRÈS la
|
||||||
surviepote$removeWidget(self, widget);
|
// création des widgets SurviePote (fin de méthode). Si une exception survient
|
||||||
}
|
// entre-temps, le menu vanilla est conservé au lieu d'être vidé.
|
||||||
|
|
||||||
Button join = Button.builder(Component.literal("Rejoindre SurviePote"), pressed -> {
|
Button join = Button.builder(Component.literal("Rejoindre SurviePote"), pressed -> {
|
||||||
ServerData data = new ServerData("SurviePote", SERVER_HOST, ServerData.Type.OTHER);
|
ServerData data = new ServerData("SurviePote", SERVER_HOST, ServerData.Type.OTHER);
|
||||||
@@ -158,6 +169,13 @@ public abstract class TitleScreenMixin {
|
|||||||
w.visible = true;
|
w.visible = true;
|
||||||
w.active = true;
|
w.active = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tout est prêt : on retire enfin les widgets vanilla (Solo, Multi, Realms,
|
||||||
|
// réseau social, icônes bas) remplacés ci-dessus.
|
||||||
|
for (GuiEventListener widget : toRemove) {
|
||||||
|
surviepote$removeWidget(self, widget);
|
||||||
|
}
|
||||||
|
|
||||||
if (!foundMulti) {
|
if (!foundMulti) {
|
||||||
NoSingleplayer.LOGGER.warn("[SurviePote] Bouton Multi introuvable, Join placé par défaut.");
|
NoSingleplayer.LOGGER.warn("[SurviePote] Bouton Multi introuvable, Join placé par défaut.");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package fr.surviepote.nosingleplayer.mixin;
|
package fr.surviepote.nosingleplayer.mixin;
|
||||||
|
|
||||||
|
import com.mojang.blaze3d.systems.RenderSystem;
|
||||||
import com.mojang.realmsclient.gui.screens.RealmsNotificationsScreen;
|
import com.mojang.realmsclient.gui.screens.RealmsNotificationsScreen;
|
||||||
import fr.surviepote.nosingleplayer.ModTextures;
|
import fr.surviepote.nosingleplayer.ModTextures;
|
||||||
import fr.surviepote.nosingleplayer.NoSingleplayer;
|
import fr.surviepote.nosingleplayer.NoSingleplayer;
|
||||||
@@ -10,12 +11,13 @@ import net.minecraft.client.gui.components.LogoRenderer;
|
|||||||
import net.minecraft.client.gui.components.SplashRenderer;
|
import net.minecraft.client.gui.components.SplashRenderer;
|
||||||
import net.minecraft.client.gui.screens.TitleScreen;
|
import net.minecraft.client.gui.screens.TitleScreen;
|
||||||
import net.minecraft.client.renderer.RenderPipelines;
|
import net.minecraft.client.renderer.RenderPipelines;
|
||||||
import net.minecraft.client.renderer.texture.DynamicTexture;
|
|
||||||
import net.minecraft.resources.Identifier;
|
import net.minecraft.resources.Identifier;
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
import org.spongepowered.asm.mixin.Unique;
|
import org.spongepowered.asm.mixin.Unique;
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
|
|
||||||
@Mixin(TitleScreen.class)
|
@Mixin(TitleScreen.class)
|
||||||
public abstract class TitleScreenRenderMixin {
|
public abstract class TitleScreenRenderMixin {
|
||||||
@@ -24,19 +26,39 @@ public abstract class TitleScreenRenderMixin {
|
|||||||
private static final Identifier SURVIEPOTE_BACKGROUND = Identifier.fromNamespaceAndPath(
|
private static final Identifier SURVIEPOTE_BACKGROUND = Identifier.fromNamespaceAndPath(
|
||||||
"nosingleplayer", "textures/gui/background.png");
|
"nosingleplayer", "textures/gui/background.png");
|
||||||
|
|
||||||
// Le PNG est lu directement dans le jar du mod (voir surviepote$loadBackground).
|
// Le PNG est lu directement dans le jar du mod (voir ModTextures).
|
||||||
@Unique
|
@Unique
|
||||||
private static final String SURVIEPOTE_BACKGROUND_RESOURCE =
|
private static final String SURVIEPOTE_BACKGROUND_RESOURCE =
|
||||||
"/assets/nosingleplayer/textures/gui/background.png";
|
"/assets/nosingleplayer/textures/gui/background.png";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Option de diagnostic : lancer le jeu avec
|
||||||
|
* {@code -Dsurviepote.customTitle=false} pour repasser au menu vanilla
|
||||||
|
* (utilisable dans les options JVM du launcher). Sert à isoler un éventuel
|
||||||
|
* souci de rendu du fond custom sur un poste donné.
|
||||||
|
*/
|
||||||
@Unique
|
@Unique
|
||||||
private static DynamicTexture surviepote$background;
|
private static final String DISABLE_CUSTOM_TITLE_PROPERTY = "surviepote.customTitle";
|
||||||
|
|
||||||
@Unique
|
@Unique
|
||||||
private static int surviepote$backgroundWidth;
|
private static boolean surviepote$gpuLogged;
|
||||||
|
|
||||||
@Unique
|
/**
|
||||||
private static int surviepote$backgroundHeight;
|
* Enregistre le fond custom AVANT le premier rendu du menu.
|
||||||
|
*
|
||||||
|
* <p>La texture GPU doit être créée hors de l'extraction du rendu : la créer
|
||||||
|
* pendant l'enregistrement des commandes de la frame invalide le frame entier
|
||||||
|
* sur les backends à command encoder (Vulkan) => menu titre entièrement noir
|
||||||
|
* (le PC affected est sous Windows + NVIDIA, qui utilise le backend Vulkan).
|
||||||
|
*/
|
||||||
|
@Inject(method = "init", at = @At("HEAD"))
|
||||||
|
private void surviepote$registerBackground(CallbackInfo ci) {
|
||||||
|
try {
|
||||||
|
ModTextures.register(SURVIEPOTE_BACKGROUND, SURVIEPOTE_BACKGROUND_RESOURCE, true);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
NoSingleplayer.LOGGER.error("[SurviePote] Enregistrement du fond custom impossible.", t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Pas de panorama dynamique : fond statique custom à la place.
|
// Pas de panorama dynamique : fond statique custom à la place.
|
||||||
// Note : extractPanorama est déclarée dans Screen (héritée), on redirige donc
|
// Note : extractPanorama est déclarée dans Screen (héritée), on redirige donc
|
||||||
@@ -46,61 +68,87 @@ public abstract class TitleScreenRenderMixin {
|
|||||||
at = @At(value = "INVOKE",
|
at = @At(value = "INVOKE",
|
||||||
target = "Lnet/minecraft/client/gui/screens/TitleScreen;extractPanorama(Lnet/minecraft/client/gui/GuiGraphicsExtractor;F)V"))
|
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) {
|
private void surviepote$customBackground(TitleScreen self, GuiGraphicsExtractor extractor, float partialTick) {
|
||||||
|
// Les widgets du menu sont dessinés par super.extractRenderState(), juste APRÈS
|
||||||
|
// cet appel. Toute exception levée ici interromp le rendu du menu entier =>
|
||||||
|
// écran 100 % noir (sans boutons). D'où les garde-fous ci-dessous.
|
||||||
|
surviepote$logGpuOnce();
|
||||||
|
|
||||||
|
// Menu vanilla si l'utilisateur a forcé -Dsurviepote.customTitle=false,
|
||||||
|
// ou si le fond custom n'est pas (ou plus) enregistré.
|
||||||
|
final boolean customEnabled = !"false".equalsIgnoreCase(
|
||||||
|
System.getProperty(DISABLE_CUSTOM_TITLE_PROPERTY, "true"));
|
||||||
|
if (!customEnabled || !ModTextures.isRegistered(SURVIEPOTE_BACKGROUND)) {
|
||||||
|
surviepote$vanillaPanorama(self, extractor, partialTick);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
int w = Minecraft.getInstance().getWindow().getGuiScaledWidth();
|
int w = Minecraft.getInstance().getWindow().getGuiScaledWidth();
|
||||||
int h = Minecraft.getInstance().getWindow().getGuiScaledHeight();
|
int h = Minecraft.getInstance().getWindow().getGuiScaledHeight();
|
||||||
|
int texW = ModTextures.getWidth(SURVIEPOTE_BACKGROUND);
|
||||||
surviepote$loadBackground();
|
int texH = ModTextures.getHeight(SURVIEPOTE_BACKGROUND);
|
||||||
if (surviepote$background == null) {
|
if (texW <= 0 || texH <= 0 || w <= 0 || h <= 0) {
|
||||||
|
surviepote$vanillaPanorama(self, extractor, partialTick);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
float screenAspect = (float) w / (float) h;
|
float screenAspect = (float) w / (float) h;
|
||||||
float texAspect = (float) surviepote$backgroundWidth / (float) surviepote$backgroundHeight;
|
float texAspect = (float) texW / (float) texH;
|
||||||
|
|
||||||
int u, v, srcW, srcH;
|
int u, v, srcW, srcH;
|
||||||
if (screenAspect > texAspect) {
|
if (screenAspect > texAspect) {
|
||||||
// Écran plus large que l'image : on rogne en haut/bas.
|
// Écran plus large que l'image : on rogne en haut/bas.
|
||||||
srcW = surviepote$backgroundWidth;
|
srcW = texW;
|
||||||
srcH = Math.round(surviepote$backgroundWidth / screenAspect);
|
srcH = Math.round(texW / screenAspect);
|
||||||
u = 0;
|
u = 0;
|
||||||
v = (surviepote$backgroundHeight - srcH) / 2;
|
v = (texH - srcH) / 2;
|
||||||
} else {
|
} else {
|
||||||
// Écran plus haut que l'image : on rogne à gauche/droite.
|
// Écran plus haut que l'image : on rogne à gauche/droite.
|
||||||
srcH = surviepote$backgroundHeight;
|
srcH = texH;
|
||||||
srcW = Math.round(surviepote$backgroundHeight * screenAspect);
|
srcW = Math.round(texH * screenAspect);
|
||||||
u = (surviepote$backgroundWidth - srcW) / 2;
|
u = (texW - srcW) / 2;
|
||||||
v = 0;
|
v = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Surcharge complète : (u, v) = origine dans la texture, (w, h) = taille à
|
// 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'é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,
|
extractor.blit(RenderPipelines.GUI_TEXTURED, SURVIEPOTE_BACKGROUND,
|
||||||
0, 0, (float) u, (float) v,
|
0, 0, (float) u, (float) v,
|
||||||
w, h, srcW, srcH,
|
w, h, srcW, srcH,
|
||||||
surviepote$backgroundWidth, surviepote$backgroundHeight, -1);
|
texW, texH, -1);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
// Repli : on bascule sur le panorama vanilla pour ne pas laisser un écran noir.
|
||||||
|
NoSingleplayer.LOGGER.error("[SurviePote] Échec du fond custom, repli sur le panorama vanilla.", t);
|
||||||
|
surviepote$vanillaPanorama(self, extractor, partialTick);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* MC 26.3 n'expose plus les ressources des mods au ResourceManager : les packs
|
* Redessine le panorama vanilla (appelé dans Screen, pas dans TitleScreen :
|
||||||
* ne sont lus que depuis des dossiers, pas depuis les jars du classpath. Une
|
* la redirection porte sur l'appel, donc pas de récursion).
|
||||||
* texture référencée par son Identifier est donc introuvable (placeholder
|
|
||||||
* magenta). Le PNG est donc lu dans le jar du mod (voir ModTextures).
|
|
||||||
*/
|
*/
|
||||||
@Unique
|
@Unique
|
||||||
private static void surviepote$loadBackground() {
|
private void surviepote$vanillaPanorama(TitleScreen self, GuiGraphicsExtractor extractor, float partialTick) {
|
||||||
DynamicTexture texture = ModTextures.get(SURVIEPOTE_BACKGROUND, SURVIEPOTE_BACKGROUND_RESOURCE,
|
try {
|
||||||
"SurviePote Background", true);
|
((ScreenAccessor) self).surviepote$extractVanillaPanorama(extractor, partialTick);
|
||||||
if (texture == null) {
|
} catch (Throwable t) {
|
||||||
surviepote$background = null;
|
NoSingleplayer.LOGGER.error("[SurviePote] Échec du panorama vanilla.", t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Log une fois les infos GPU : permet de comparer un poste qui plante d'un poste qui marche. */
|
||||||
|
@Unique
|
||||||
|
private static void surviepote$logGpuOnce() {
|
||||||
|
if (surviepote$gpuLogged) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (surviepote$background != texture) {
|
surviepote$gpuLogged = true;
|
||||||
surviepote$background = texture;
|
try {
|
||||||
surviepote$backgroundWidth = texture.getPixels().getWidth();
|
var info = RenderSystem.getDevice().getDeviceInfo();
|
||||||
surviepote$backgroundHeight = texture.getPixels().getHeight();
|
NoSingleplayer.LOGGER.info("[SurviePote] GPU: {} | {} | pilote {} | backend {}",
|
||||||
NoSingleplayer.LOGGER.info("[SurviePote] Fond chargé : {}x{}",
|
info.name(), info.vendorName(), info.driverInfo(), info.backendName());
|
||||||
surviepote$backgroundWidth, surviepote$backgroundHeight);
|
} catch (Throwable t) {
|
||||||
|
NoSingleplayer.LOGGER.warn("[SurviePote] Infos GPU indisponibles : {}", t.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 274 KiB |
|
Before Width: | Height: | Size: 274 KiB |
|
Before Width: | Height: | Size: 274 KiB |
|
Before Width: | Height: | Size: 274 KiB |
|
Before Width: | Height: | Size: 274 KiB |
|
Before Width: | Height: | Size: 274 KiB |
|
Before Width: | Height: | Size: 98 B |
|
Before Width: | Height: | Size: 98 B |
|
Before Width: | Height: | Size: 1.9 MiB After Width: | Height: | Size: 826 KiB |
@@ -63,8 +63,7 @@ Tri effectué par inspection réelle de `fabric.mod.json` (`environment` + `entr
|
|||||||
|
|
||||||
| Mod | raison |
|
| Mod | raison |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| spatial-gui-1.8+26.3-fabric.jar | `environment: client` (lib GUI, dépend de yet_another_config_lib_v3) |
|
| yet_another_config_lib_v3-3.9.7+26.3-fabric.jar | entrypoint `client` uniquement (lib de config ; utilisée par le bridge YACL de PatPat, avec fallback cloth-config) |
|
||||||
| yet_another_config_lib_v3-3.9.7+26.3-fabric.jar | entrypoint `client` uniquement (lib de config) |
|
|
||||||
|
|
||||||
## Exclus (15) — client-only, NE PAS copier sur le serveur
|
## Exclus (15) — client-only, NE PAS copier sur le serveur
|
||||||
|
|
||||||
@@ -94,3 +93,8 @@ Notes :
|
|||||||
`atlas/.../fabricmods/required/` clients, et config `config/immersiveores/`). Voir `quarantine/README.md`.
|
`atlas/.../fabricmods/required/` clients, et config `config/immersiveores/`). Voir `quarantine/README.md`.
|
||||||
- `simplebackpacks-fabric-26.3-5.2.1.jar` : datapack cassé (recipes/advancements) → `Failed to load
|
- `simplebackpacks-fabric-26.3-5.2.1.jar` : datapack cassé (recipes/advancements) → `Failed to load
|
||||||
registries` → crash serveur ET client. Voir `quarantine/README.md`. Retiré partout.
|
registries` → crash serveur ET client. Voir `quarantine/README.md`. Retiré partout.
|
||||||
|
- `spatial-gui-1.8+26.3-fabric.jar` : `environment: client` — GUI d'inventaire rendue en plan 3D,
|
||||||
|
retirée du pack le 20/10/2026 (rendu moche + instabilité). Retiré de
|
||||||
|
`atlas/.../fabricmods/required/` (clients) et de `distribution.json`.
|
||||||
|
`yet_another_config_lib_v3` est **conservée** : elle sert au bridge YACL de PatPat (fallback
|
||||||
|
`cloth-config` sinon). Absente de Prism et du serveur, donc aucun retrait côté serveur.
|
||||||
|
|||||||
@@ -16,10 +16,11 @@ SERVER_EXCLUDE=(
|
|||||||
)
|
)
|
||||||
# Mods à exclure de l'ATLAS (= clients) : client-only purs + mods cassés
|
# Mods à exclure de l'ATLAS (= clients) : client-only purs + mods cassés
|
||||||
# (simplebackpacks, ImmersiveOres — voir quarantine/README.md).
|
# (simplebackpacks, ImmersiveOres — voir quarantine/README.md).
|
||||||
|
# spatial-gui : retiré du pack le 20/10/2026 (GUI 3D disgracieuse) — voir server/mods/SOURCE.md.
|
||||||
ATLAS_EXCLUDE=(
|
ATLAS_EXCLUDE=(
|
||||||
sodium-fabric iris-fabric continuity- dynamic-fps- fast-ip-ping modmenu- morechathistory-
|
sodium-fabric iris-fabric continuity- dynamic-fps- fast-ip-ping modmenu- morechathistory-
|
||||||
capes- chat_heads- skinlayers3d waveycapes xaerominimap xaeroworldmap
|
capes- chat_heads- skinlayers3d waveycapes xaerominimap xaeroworldmap
|
||||||
appleskin- vanillarice- simplebackpacks- ImmersiveOres-26.3
|
appleskin- vanillarice- simplebackpacks- ImmersiveOres-26.3 spatial-gui-
|
||||||
)
|
)
|
||||||
|
|
||||||
excluded() { local f="$1"; shift; for p in "$@"; do [[ "$f" == "$p"* ]] && return 0; done; return 1; }
|
excluded() { local f="$1"; shift; for p in "$@"; do [[ "$f" == "$p"* ]] && return 0; done; return 1; }
|
||||||
@@ -46,6 +47,8 @@ done
|
|||||||
# Nettoyage : simplebackpacks et ImmersiveOres ne doivent être ni serveur ni atlas
|
# Nettoyage : simplebackpacks et ImmersiveOres ne doivent être ni serveur ni atlas
|
||||||
rm -f "$SRV_MODS"/simplebackpacks-*.jar "$ATLAS_MODS"/simplebackpacks-*.jar
|
rm -f "$SRV_MODS"/simplebackpacks-*.jar "$ATLAS_MODS"/simplebackpacks-*.jar
|
||||||
rm -f "$SRV_MODS"/ImmersiveOres-*.jar "$ATLAS_MODS"/ImmersiveOres-*.jar server/mods-client-only/ImmersiveOres-*.jar
|
rm -f "$SRV_MODS"/ImmersiveOres-*.jar "$ATLAS_MODS"/ImmersiveOres-*.jar server/mods-client-only/ImmersiveOres-*.jar
|
||||||
|
# spatial-gui : retiré du pack le 20/10/2026 (client-only, jamais sur le serveur).
|
||||||
|
rm -f "$SRV_MODS"/spatial-gui-*.jar "$ATLAS_MODS"/spatial-gui-*.jar
|
||||||
[ -f quarantine/simplebackpacks-fabric-26.3-5.2.1.jar ] || echo "NOTE: simplebackpacks en quarantaine (voir quarantine/README.md)"
|
[ -f quarantine/simplebackpacks-fabric-26.3-5.2.1.jar ] || echo "NOTE: simplebackpacks en quarantaine (voir quarantine/README.md)"
|
||||||
|
|
||||||
echo "== resourcepack/shader (Prism -> atlas files/) =="
|
echo "== resourcepack/shader (Prism -> atlas files/) =="
|
||||||
|
|||||||