Ladescreen hinzugefügt. hat noch einen Bug bezüglich der positionierung des fensters
This commit is contained in:
@@ -23,6 +23,9 @@ import org.lwjgl.glfw.GLFW;
|
|||||||
import org.lwjgl.glfw.GLFWVidMode;
|
import org.lwjgl.glfw.GLFWVidMode;
|
||||||
import org.lwjgl.system.MemoryStack;
|
import org.lwjgl.system.MemoryStack;
|
||||||
import com.jme3.system.lwjgl.LwjglWindow;
|
import com.jme3.system.lwjgl.LwjglWindow;
|
||||||
|
import de.blight.game.state.HotbarState;
|
||||||
|
import de.blight.game.state.HudState;
|
||||||
|
import de.blight.game.state.LoadingScreenState;
|
||||||
|
|
||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
import java.nio.IntBuffer;
|
import java.nio.IntBuffer;
|
||||||
@@ -50,14 +53,16 @@ public class BlightGame extends SimpleApplication {
|
|||||||
private AudioScreen audioScreen;
|
private AudioScreen audioScreen;
|
||||||
private PauseMenu pauseMenu;
|
private PauseMenu pauseMenu;
|
||||||
private MainMenuState mainMenuState;
|
private MainMenuState mainMenuState;
|
||||||
|
private LoadingScreenState loadingScreen;
|
||||||
|
|
||||||
/** Routed durch onClose-Callbacks, damit Config/Grafik-Screen zurück zum richtigen Menü springen. */
|
/** Routed durch onClose-Callbacks, damit Config/Grafik-Screen zurück zum richtigen Menü springen. */
|
||||||
private Runnable screenCloseTarget;
|
private Runnable screenCloseTarget;
|
||||||
|
|
||||||
// Vollbild-Watchdog: erkennt, wenn das Fenster unerwartet die Auflösung wechselt
|
private long startTimeMs = 0;
|
||||||
// (z.B. GNOME Shell de-fullscreent OverrideRedirect-Fenster bei Monitorfokus-Wechsel)
|
private long lastFullscreenFix = 0;
|
||||||
private long startTimeMs = 0;
|
private long pendingFullscreenFixAt = Long.MAX_VALUE;
|
||||||
private long lastFullscreenFix = 0;
|
private boolean firstLaunch = true; // false nach dem ersten simpleInitApp()
|
||||||
|
private boolean initialFixDone = false; // Initial-Fix bei 0.5s bereits gelaufen
|
||||||
|
|
||||||
private JWindow splashWindow;
|
private JWindow splashWindow;
|
||||||
|
|
||||||
@@ -101,6 +106,12 @@ public class BlightGame extends SimpleApplication {
|
|||||||
settings.setBitsPerPixel(32);
|
settings.setBitsPerPixel(32);
|
||||||
settings.setVSync(gs.vsync);
|
settings.setVSync(gs.vsync);
|
||||||
settings.setSamples(gs.samples);
|
settings.setSamples(gs.samples);
|
||||||
|
if (!gs.fullscreen) {
|
||||||
|
// Fenster beim Start auf dem Primär-Monitor zentrieren.
|
||||||
|
// Ohne diesen Hint öffnet GLFW das Fenster bei (0,0) oder wo der WM will –
|
||||||
|
// bei Editor-Start typisch auf dem Sekundär-Monitor.
|
||||||
|
settings.setCenterWindow(true);
|
||||||
|
}
|
||||||
// Nur unter X11-Linux: GLFW auf X11-Backend festlegen, damit libdecor keine
|
// Nur unter X11-Linux: GLFW auf X11-Backend festlegen, damit libdecor keine
|
||||||
// Fensterrahmen im Vollbild hinzufügt. Unter nativem Wayland nicht setzen.
|
// Fensterrahmen im Vollbild hinzufügt. Unter nativem Wayland nicht setzen.
|
||||||
if ("x11".equalsIgnoreCase(System.getenv("XDG_SESSION_TYPE"))) {
|
if ("x11".equalsIgnoreCase(System.getenv("XDG_SESSION_TYPE"))) {
|
||||||
@@ -188,14 +199,23 @@ public class BlightGame extends SimpleApplication {
|
|||||||
audioSettings = AudioSettingsStore.load();
|
audioSettings = AudioSettingsStore.load();
|
||||||
|
|
||||||
startTimeMs = System.currentTimeMillis();
|
startTimeMs = System.currentTimeMillis();
|
||||||
|
initialFixDone = false;
|
||||||
if (graphicsSettings.fullscreen) {
|
if (graphicsSettings.fullscreen) {
|
||||||
// Tatsächliche Cam-Auflösung als Watchdog-Baseline (main() nutzt native Monitorgröße)
|
log.info("[Grafik] Vollbild-Start: cam={}×{} fullscreen={} gewünschte Aufl.={}×{}",
|
||||||
graphicsSettings.width = cam.getWidth();
|
cam.getWidth(), cam.getHeight(), graphicsSettings.fullscreen,
|
||||||
graphicsSettings.height = cam.getHeight();
|
graphicsSettings.width, graphicsSettings.height);
|
||||||
log.info("[Grafik] Vollbild-Start: cam={}×{} fullscreen={}",
|
if (firstLaunch) {
|
||||||
cam.getWidth(), cam.getHeight(), graphicsSettings.fullscreen);
|
pendingFullscreenFixAt = Long.MAX_VALUE;
|
||||||
fixFullscreenMonitor();
|
// Fenster NICHT verstecken: der Toggle windowed→fullscreen in fixFullscreenMonitor()
|
||||||
|
// funktioniert nur zuverlässig, wenn das Fenster bereits in einem aktiven
|
||||||
|
// Fullscreen-Zustand war (wie beim 5s-Watchdog). Aus dem Hidden-Zustand behandelt
|
||||||
|
// Cinnamon den nächsten Fullscreen-Übergang als Neuerstplatzierung und ignoriert
|
||||||
|
// den Ziel-Monitor zugunsten des aktiven Workspaces (Editor-Monitor).
|
||||||
|
}
|
||||||
|
// Restart nach Einstellungsänderung: pendingFullscreenFixAt (scheduleFullscreenFix)
|
||||||
|
// bleibt gesetzt und übernimmt den Fix nach 1 s.
|
||||||
}
|
}
|
||||||
|
firstLaunch = false;
|
||||||
stateManager.attach(new AudioSettingsState(audioSettings));
|
stateManager.attach(new AudioSettingsState(audioSettings));
|
||||||
|
|
||||||
status("Lade Spielstand...");
|
status("Lade Spielstand...");
|
||||||
@@ -207,6 +227,12 @@ public class BlightGame extends SimpleApplication {
|
|||||||
worldScene.setEnabled(false);
|
worldScene.setEnabled(false);
|
||||||
stateManager.attach(worldScene);
|
stateManager.attach(worldScene);
|
||||||
|
|
||||||
|
// Ladebildschirm – wird von startWorld() aktiviert und löst worldScene.setEnabled(true)
|
||||||
|
// erst nach einem gerenderten Frame aus (kein schwarzer Startbildschirm).
|
||||||
|
loadingScreen = new LoadingScreenState();
|
||||||
|
loadingScreen.setEnabled(false);
|
||||||
|
stateManager.attach(loadingScreen);
|
||||||
|
|
||||||
// screenCloseTarget: dynamisch gesetzt, je nachdem ob Config/Grafik vom Hauptmenü
|
// screenCloseTarget: dynamisch gesetzt, je nachdem ob Config/Grafik vom Hauptmenü
|
||||||
// oder vom Pausemenü geöffnet wurde.
|
// oder vom Pausemenü geöffnet wurde.
|
||||||
screenCloseTarget = () -> {};
|
screenCloseTarget = () -> {};
|
||||||
@@ -388,8 +414,16 @@ public class BlightGame extends SimpleApplication {
|
|||||||
if (mainMenuState != null) {
|
if (mainMenuState != null) {
|
||||||
mainMenuState.setEnabled(false);
|
mainMenuState.setEnabled(false);
|
||||||
}
|
}
|
||||||
worldScene.setEnabled(true);
|
|
||||||
inputManager.setCursorVisible(false);
|
inputManager.setCursorVisible(false);
|
||||||
|
loadingScreen.setEnabled(true);
|
||||||
|
// LoadingScreenState löst worldScene.setEnabled(true) nach dem ersten gerenderten
|
||||||
|
// Ladebildschirm-Frame aus, damit der Spieler keinen schwarzen Bildschirm sieht.
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Wird von GraphicsScreen aufgerufen bevor app.restart() – sorgt dafür, dass
|
||||||
|
* fixFullscreenMonitor() ~1 s nach dem Neustart läuft (WM braucht Zeit zum Stabilisieren). */
|
||||||
|
public void scheduleFullscreenFix() {
|
||||||
|
pendingFullscreenFixAt = System.currentTimeMillis() + 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Render-Loop ──────────────────────────────────────────────────────────
|
// ── Render-Loop ──────────────────────────────────────────────────────────
|
||||||
@@ -400,11 +434,26 @@ public class BlightGame extends SimpleApplication {
|
|||||||
gameReady = true;
|
gameReady = true;
|
||||||
status("Bereit");
|
status("Bereit");
|
||||||
}
|
}
|
||||||
// Vollbild-Watchdog: Wenn GNOME Shell oder der WM das Fenster unerwartet aus dem
|
long now = System.currentTimeMillis();
|
||||||
// Vollbild-Modus holt (erkennbar an falscher Auflösung), sofort wiederherstellen.
|
|
||||||
// 5s Anlaufzeit überspringen (Initialisierungs-Resize-Events), 3s Cooldown.
|
// Initial-Fix: 200 ms nach dem ersten Start. Fenster war die ganze Zeit sichtbar
|
||||||
|
// (kein glfwHideWindow) → Cinnamon kennt es als "existing fullscreen window" →
|
||||||
|
// Toggle windowed→fullscreen respektiert Ziel-Monitor (identisch zum Watchdog-Pfad).
|
||||||
|
if (!initialFixDone && graphicsSettings != null && graphicsSettings.fullscreen
|
||||||
|
&& now - startTimeMs > 200) {
|
||||||
|
initialFixDone = true;
|
||||||
|
lastFullscreenFix = now;
|
||||||
|
fixFullscreenMonitor();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (now >= pendingFullscreenFixAt) {
|
||||||
|
pendingFullscreenFixAt = Long.MAX_VALUE;
|
||||||
|
lastFullscreenFix = now;
|
||||||
|
fixFullscreenMonitor();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Watchdog: Vollbild-Verlust durch GNOME/WM (erkennbar an Auflösungs-Mismatch).
|
||||||
if (graphicsSettings != null && graphicsSettings.fullscreen) {
|
if (graphicsSettings != null && graphicsSettings.fullscreen) {
|
||||||
long now = System.currentTimeMillis();
|
|
||||||
if (now - startTimeMs > 5_000
|
if (now - startTimeMs > 5_000
|
||||||
&& (cam.getWidth() != graphicsSettings.width
|
&& (cam.getWidth() != graphicsSettings.width
|
||||||
|| cam.getHeight() != graphicsSettings.height)
|
|| cam.getHeight() != graphicsSettings.height)
|
||||||
@@ -417,6 +466,28 @@ public class BlightGame extends SimpleApplication {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reshape(int w, int h) {
|
||||||
|
super.reshape(w, h);
|
||||||
|
if (gameReady) {
|
||||||
|
rebuildScreenDependentStates();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void rebuildScreenDependentStates() {
|
||||||
|
rebuildState(HudState.class);
|
||||||
|
rebuildState(HotbarState.class);
|
||||||
|
rebuildState(LoadingScreenState.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
private <T extends com.jme3.app.state.BaseAppState> void rebuildState(Class<T> cls) {
|
||||||
|
T state = stateManager.getState(cls);
|
||||||
|
if (state != null && state.isEnabled()) {
|
||||||
|
state.setEnabled(false);
|
||||||
|
state.setEnabled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ── Konsolen-Befehle ─────────────────────────────────────────────────────
|
// ── Konsolen-Befehle ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
private void registerGameCommands(JmeConsole console) {
|
private void registerGameCommands(JmeConsole console) {
|
||||||
@@ -482,6 +553,14 @@ public class BlightGame extends SimpleApplication {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Vollbild sicherstellen: GLFW ist hier bereits durch JME3 initialisiert.
|
// Vollbild sicherstellen: GLFW ist hier bereits durch JME3 initialisiert.
|
||||||
|
/** Thread.sleep + glfwPollEvents: garantiertes Zeitfenster für WM-Reaktionen.
|
||||||
|
* glfwWaitEventsTimeout() kehrt beim ersten Event zurück, nicht nach voller Zeit. */
|
||||||
|
private static void glfwSleep(long ms) {
|
||||||
|
GLFW.glfwPollEvents();
|
||||||
|
try { Thread.sleep(ms); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
|
||||||
|
GLFW.glfwPollEvents();
|
||||||
|
}
|
||||||
|
|
||||||
// Problem: JME3 erstellt das Fenster sofort sichtbar. Cinnamon/Muffin (GLFW 3.5 nutzt
|
// Problem: JME3 erstellt das Fenster sofort sichtbar. Cinnamon/Muffin (GLFW 3.5 nutzt
|
||||||
// _NET_WM_STATE_FULLSCREEN statt OverrideRedirect) schiebt das Fenster beim ersten
|
// _NET_WM_STATE_FULLSCREEN statt OverrideRedirect) schiebt das Fenster beim ersten
|
||||||
// Erscheinen um eine Panel-Höhe nach unten (z.B. Y+32), bevor der WM den State
|
// Erscheinen um eine Panel-Höhe nach unten (z.B. Y+32), bevor der WM den State
|
||||||
@@ -503,34 +582,20 @@ public class BlightGame extends SimpleApplication {
|
|||||||
// Ggf. iconifiziertes Fenster wiederherstellen
|
// Ggf. iconifiziertes Fenster wiederherstellen
|
||||||
GLFW.glfwRestoreWindow(win);
|
GLFW.glfwRestoreWindow(win);
|
||||||
|
|
||||||
// Ist das Fenster bereits in GLFW-Vollbild (OverrideRedirect)?
|
long target = GLFW.glfwGetPrimaryMonitor();
|
||||||
long currentMonitor = GLFW.glfwGetWindowMonitor(win);
|
long currentMonitor = GLFW.glfwGetWindowMonitor(win);
|
||||||
|
|
||||||
// AWT-Primary-Monitor mit GLFW-Monitoren abgleichen
|
|
||||||
Rectangle awt = GraphicsEnvironment
|
|
||||||
.getLocalGraphicsEnvironment()
|
|
||||||
.getDefaultScreenDevice()
|
|
||||||
.getDefaultConfiguration()
|
|
||||||
.getBounds();
|
|
||||||
|
|
||||||
PointerBuffer mons = GLFW.glfwGetMonitors();
|
PointerBuffer mons = GLFW.glfwGetMonitors();
|
||||||
if (mons == null || mons.limit() == 0) {
|
if (mons != null) {
|
||||||
log.warn("[Grafik] glfwGetMonitors() liefert nichts");
|
try (MemoryStack stack = MemoryStack.stackPush()) {
|
||||||
GLFW.glfwFocusWindow(win);
|
IntBuffer mx = stack.mallocInt(1);
|
||||||
return;
|
IntBuffer my = stack.mallocInt(1);
|
||||||
}
|
for (int i = 0; i < mons.limit(); i++) {
|
||||||
|
GLFW.glfwGetMonitorPos(mons.get(i), mx, my);
|
||||||
long target = GLFW.glfwGetPrimaryMonitor();
|
log.info("[Grafik] GLFW-Monitor {}: pos=({},{}), primary={}, current={}",
|
||||||
try (MemoryStack stack = MemoryStack.stackPush()) {
|
i, mx.get(0), my.get(0),
|
||||||
IntBuffer mx = stack.mallocInt(1);
|
(target == mons.get(i)) ? "JA" : "nein",
|
||||||
IntBuffer my = stack.mallocInt(1);
|
(currentMonitor == mons.get(i)) ? "JA" : "nein");
|
||||||
for (int i = 0; i < mons.limit(); i++) {
|
|
||||||
GLFW.glfwGetMonitorPos(mons.get(i), mx, my);
|
|
||||||
log.info("[Grafik] GLFW-Monitor {}: pos=({},{}) | AWT-Primary: ({},{}) | aktuellerMonitor={}",
|
|
||||||
i, mx.get(0), my.get(0), awt.x, awt.y,
|
|
||||||
(currentMonitor == mons.get(i)) ? "JA" : "nein");
|
|
||||||
if (mx.get(0) == awt.x && my.get(0) == awt.y) {
|
|
||||||
target = mons.get(i);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -541,16 +606,39 @@ public class BlightGame extends SimpleApplication {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Windowed → Fullscreen Toggle:
|
int fsW = (graphicsSettings != null && graphicsSettings.width > 0) ? graphicsSettings.width : vm.width();
|
||||||
// JME3 erstellt das Fenster sofort sichtbar; Cinnamon setzt Y+32 (Panel-Offset)
|
int fsH = (graphicsSettings != null && graphicsSettings.height > 0) ? graphicsSettings.height : vm.height();
|
||||||
// bevor _NET_WM_STATE_FULLSCREEN vollständig verarbeitet ist.
|
|
||||||
// Kurz in den Fenstermodus wechseln und zurück → WM verarbeitet den
|
// Primärmonitor-Position für den Windowed-Schritt
|
||||||
// State neu und setzt Y korrekt auf Monitor-Origin.
|
int primX = 0, primY = 0;
|
||||||
log.info("[Grafik] Vollbild-Fix: Toggle Fenstermodus → Vollbild auf Monitor {} ({}×{}@{}Hz)",
|
try (MemoryStack stack = MemoryStack.stackPush()) {
|
||||||
target, vm.width(), vm.height(), vm.refreshRate());
|
IntBuffer px = stack.mallocInt(1);
|
||||||
GLFW.glfwSetWindowMonitor(win, 0L, awt.x, awt.y, vm.width(), vm.height(), 0);
|
IntBuffer py = stack.mallocInt(1);
|
||||||
GLFW.glfwWaitEventsTimeout(0.1);
|
GLFW.glfwGetMonitorPos(target, px, py);
|
||||||
GLFW.glfwSetWindowMonitor(win, target, 0, 0, vm.width(), vm.height(), vm.refreshRate());
|
primX = px.get(0);
|
||||||
|
primY = py.get(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fenster VOR dem Toggle anzeigen und fokussieren.
|
||||||
|
// Cinnamon respektiert beim windowed-Schritt die angeforderte Position nur dann,
|
||||||
|
// wenn das Fenster fokussiert ist. Ohne Fokus verschiebt Cinnamon das Fenster
|
||||||
|
// auf den aktiven Workspace (Editor-Monitor) → der Fullscreen-Schritt danach
|
||||||
|
// landet dann ebenfalls auf dem falschen Monitor.
|
||||||
|
GLFW.glfwShowWindow(win);
|
||||||
|
GLFW.glfwFocusWindow(win);
|
||||||
|
glfwSleep(100); // WM-Fokuswechsel abwarten
|
||||||
|
|
||||||
|
// Toggle Vollbild → Fenster → Vollbild auf Primär-Monitor.
|
||||||
|
log.info("[Grafik] Vollbild-Fix: Toggle → Vollbild {}×{} auf Monitor {} (@{}Hz)",
|
||||||
|
fsW, fsH, target, vm.refreshRate());
|
||||||
|
GLFW.glfwSetWindowMonitor(win, 0L, primX, primY, fsW, fsH, 0);
|
||||||
|
// Cursor auf Zielmonitor warpen: Muffin/Cinnamon nutzt meta_screen_get_current_monitor()
|
||||||
|
// (= Cursor-Position), nicht die Fensterposition, für Fullscreen-Placement.
|
||||||
|
// glfwSetCursorPos ist relativ zum Fenster → Mitte des Fensters = Mitte des Zielmonitors.
|
||||||
|
GLFW.glfwSetCursorPos(win, fsW / 2.0, fsH / 2.0);
|
||||||
|
glfwSleep(80);
|
||||||
|
GLFW.glfwSetWindowMonitor(win, target, 0, 0, fsW, fsH, vm.refreshRate());
|
||||||
|
glfwSleep(200);
|
||||||
GLFW.glfwFocusWindow(win);
|
GLFW.glfwFocusWindow(win);
|
||||||
|
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
|
|||||||
@@ -4,14 +4,22 @@ import com.jme3.font.BitmapText;
|
|||||||
import com.jme3.math.ColorRGBA;
|
import com.jme3.math.ColorRGBA;
|
||||||
import com.jme3.scene.Node;
|
import com.jme3.scene.Node;
|
||||||
import com.jme3.system.AppSettings;
|
import com.jme3.system.AppSettings;
|
||||||
|
import com.jme3.system.lwjgl.LwjglWindow;
|
||||||
|
import de.blight.game.BlightGame;
|
||||||
import de.blight.game.state.TerrainChunkState;
|
import de.blight.game.state.TerrainChunkState;
|
||||||
import de.blight.game.state.WeatherState;
|
import de.blight.game.state.WeatherState;
|
||||||
import de.blight.lang.TextResolver;
|
import de.blight.lang.TextResolver;
|
||||||
|
import org.lwjgl.glfw.GLFW;
|
||||||
|
import org.lwjgl.glfw.GLFWVidMode;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public class GraphicsScreen extends MenuScreen {
|
public class GraphicsScreen extends MenuScreen {
|
||||||
|
|
||||||
private static final int[][] RESOLUTIONS = {
|
private static final int[][] FALLBACK_RESOLUTIONS = {
|
||||||
{1280, 720}, {1600, 900}, {1920, 1080}, {2560, 1440}, {3840, 2160}
|
{1024, 768}, {1280, 720}, {1600, 900}, {1920, 1080}, {2560, 1440}, {3840, 2160}
|
||||||
};
|
};
|
||||||
private static final int[] SAMPLES = {0, 2, 4, 8};
|
private static final int[] SAMPLES = {0, 2, 4, 8};
|
||||||
|
|
||||||
@@ -27,6 +35,7 @@ public class GraphicsScreen extends MenuScreen {
|
|||||||
private GraphicsSettings edit;
|
private GraphicsSettings edit;
|
||||||
private final Runnable onClose;
|
private final Runnable onClose;
|
||||||
|
|
||||||
|
private int[][] resolutions = FALLBACK_RESOLUTIONS;
|
||||||
private int resIdx;
|
private int resIdx;
|
||||||
private int samplesIdx;
|
private int samplesIdx;
|
||||||
|
|
||||||
@@ -44,6 +53,8 @@ public class GraphicsScreen extends MenuScreen {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onEnableExtras() {
|
protected void onEnableExtras() {
|
||||||
|
resolutions = buildResolutionList();
|
||||||
|
|
||||||
edit = new GraphicsSettings();
|
edit = new GraphicsSettings();
|
||||||
edit.width = live.width; edit.height = live.height;
|
edit.width = live.width; edit.height = live.height;
|
||||||
edit.fullscreen = live.fullscreen;
|
edit.fullscreen = live.fullscreen;
|
||||||
@@ -53,8 +64,8 @@ public class GraphicsScreen extends MenuScreen {
|
|||||||
edit.shadowQuality = live.shadowQuality;
|
edit.shadowQuality = live.shadowQuality;
|
||||||
|
|
||||||
resIdx = 0;
|
resIdx = 0;
|
||||||
for (int i = 0; i < RESOLUTIONS.length; i++) {
|
for (int i = 0; i < resolutions.length; i++) {
|
||||||
if (RESOLUTIONS[i][0] == edit.width && RESOLUTIONS[i][1] == edit.height) {
|
if (resolutions[i][0] == edit.width && resolutions[i][1] == edit.height) {
|
||||||
resIdx = i; break;
|
resIdx = i; break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -118,7 +129,7 @@ public class GraphicsScreen extends MenuScreen {
|
|||||||
|
|
||||||
private void refreshText(int row) {
|
private void refreshText(int row) {
|
||||||
String val = switch (row) {
|
String val = switch (row) {
|
||||||
case ROW_RES -> RESOLUTIONS[resIdx][0] + "x" + RESOLUTIONS[resIdx][1];
|
case ROW_RES -> resolutions[resIdx][0] + "x" + resolutions[resIdx][1];
|
||||||
case ROW_FULL -> edit.fullscreen ? t("menu.graphics.val.on") : t("menu.graphics.val.off");
|
case ROW_FULL -> edit.fullscreen ? t("menu.graphics.val.on") : t("menu.graphics.val.off");
|
||||||
case ROW_VSYNC -> edit.vsync ? t("menu.graphics.val.on") : t("menu.graphics.val.off");
|
case ROW_VSYNC -> edit.vsync ? t("menu.graphics.val.on") : t("menu.graphics.val.off");
|
||||||
case ROW_AA -> SAMPLES[samplesIdx] == 0
|
case ROW_AA -> SAMPLES[samplesIdx] == 0
|
||||||
@@ -148,9 +159,9 @@ public class GraphicsScreen extends MenuScreen {
|
|||||||
private void cycleRow(int row, int dir) {
|
private void cycleRow(int row, int dir) {
|
||||||
switch (row) {
|
switch (row) {
|
||||||
case ROW_RES:
|
case ROW_RES:
|
||||||
resIdx = (resIdx + dir + RESOLUTIONS.length) % RESOLUTIONS.length;
|
resIdx = (resIdx + dir + resolutions.length) % resolutions.length;
|
||||||
edit.width = RESOLUTIONS[resIdx][0];
|
edit.width = resolutions[resIdx][0];
|
||||||
edit.height = RESOLUTIONS[resIdx][1];
|
edit.height = resolutions[resIdx][1];
|
||||||
break;
|
break;
|
||||||
case ROW_FULL: edit.fullscreen = !edit.fullscreen; break;
|
case ROW_FULL: edit.fullscreen = !edit.fullscreen; break;
|
||||||
case ROW_VSYNC: edit.vsync = !edit.vsync; break;
|
case ROW_VSYNC: edit.vsync = !edit.vsync; break;
|
||||||
@@ -204,6 +215,9 @@ public class GraphicsScreen extends MenuScreen {
|
|||||||
s.setVSync(live.vsync);
|
s.setVSync(live.vsync);
|
||||||
s.setSamples(live.samples);
|
s.setSamples(live.samples);
|
||||||
app.setSettings(s);
|
app.setSettings(s);
|
||||||
|
if (live.fullscreen) {
|
||||||
|
((BlightGame) getApplication()).scheduleFullscreenFix();
|
||||||
|
}
|
||||||
app.restart();
|
app.restart();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -213,5 +227,41 @@ public class GraphicsScreen extends MenuScreen {
|
|||||||
if (onClose != null) onClose.run();
|
if (onClose != null) onClose.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int[][] buildResolutionList() {
|
||||||
|
try {
|
||||||
|
com.jme3.system.JmeContext ctx = getApplication().getContext();
|
||||||
|
if (!(ctx instanceof LwjglWindow)) {
|
||||||
|
return FALLBACK_RESOLUTIONS;
|
||||||
|
}
|
||||||
|
long monitor = GLFW.glfwGetPrimaryMonitor();
|
||||||
|
if (monitor == 0L) {
|
||||||
|
return FALLBACK_RESOLUTIONS;
|
||||||
|
}
|
||||||
|
GLFWVidMode.Buffer modes = GLFW.glfwGetVideoModes(monitor);
|
||||||
|
if (modes == null || modes.limit() == 0) {
|
||||||
|
return FALLBACK_RESOLUTIONS;
|
||||||
|
}
|
||||||
|
// Eindeutige (w, h) Paare sammeln — Duplikate (gleiche Auflösung, anderer Refresh) entfernen
|
||||||
|
LinkedHashSet<Long> seen = new LinkedHashSet<>();
|
||||||
|
List<int[]> list = new ArrayList<>();
|
||||||
|
for (int i = 0; i < modes.limit(); i++) {
|
||||||
|
modes.position(i);
|
||||||
|
int w = modes.width();
|
||||||
|
int h = modes.height();
|
||||||
|
long key = ((long) w << 32) | (h & 0xFFFFFFFFL);
|
||||||
|
if (w >= 1024 && h >= 600 && seen.add(key)) {
|
||||||
|
list.add(new int[]{w, h});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (list.isEmpty()) {
|
||||||
|
return FALLBACK_RESOLUTIONS;
|
||||||
|
}
|
||||||
|
list.sort((a, b) -> a[0] != b[0] ? Integer.compare(a[0], b[0]) : Integer.compare(a[1], b[1]));
|
||||||
|
return list.toArray(new int[0][]);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
return FALLBACK_RESOLUTIONS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static String t(String id) { return TextResolver.get().resolveId(id); }
|
private static String t(String id) { return TextResolver.get().resolveId(id); }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -371,9 +371,19 @@ public class WorldScene extends BaseAppState {
|
|||||||
new JobProgressAdapter<LightProbe>() {
|
new JobProgressAdapter<LightProbe>() {
|
||||||
@Override public void done(LightProbe result) {
|
@Override public void done(LightProbe result) {
|
||||||
dayNight.getDayTime().setTimeOfDay(restoreTime);
|
dayNight.getDayTime().setTimeOfDay(restoreTime);
|
||||||
app.getStateManager().detach(envCam);
|
// 2 Frames warten: JME3 braucht 1-2 Frames um die neue
|
||||||
envCam = null;
|
// Probe auf die GPU zu übertragen. isGameReady() prüft
|
||||||
log.info("[WorldScene] LightProbe fertig, Uhrzeit zurück auf {}.", restoreTime);
|
// envCam == null, daher erst dann setzen wenn die Probe
|
||||||
|
// für den Renderer sichtbar ist.
|
||||||
|
app.enqueue(() -> {
|
||||||
|
app.enqueue(() -> {
|
||||||
|
app.getStateManager().detach(envCam);
|
||||||
|
envCam = null;
|
||||||
|
log.info("[WorldScene] LightProbe fertig, Uhrzeit zurück auf {}.", restoreTime);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
return null;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1391,6 +1401,10 @@ public class WorldScene extends BaseAppState {
|
|||||||
* Teleportiert den Spieler zu (x, y, z). Wenn y == Float.NaN wird die
|
* Teleportiert den Spieler zu (x, y, z). Wenn y == Float.NaN wird die
|
||||||
* Terrainhöhe an (x, z) verwendet und ein kleiner Offset nach oben addiert.
|
* Terrainhöhe an (x, z) verwendet und ein kleiner Offset nach oben addiert.
|
||||||
*/
|
*/
|
||||||
|
public boolean isGameReady() {
|
||||||
|
return !physicsCharPending && animContextReady && envCam == null;
|
||||||
|
}
|
||||||
|
|
||||||
public String teleportPlayer(float x, float y, float z) {
|
public String teleportPlayer(float x, float y, float z) {
|
||||||
if (physicsChar == null) return "Spieler noch nicht initialisiert";
|
if (physicsChar == null) return "Spieler noch nicht initialisiert";
|
||||||
if (Float.isNaN(y)) {
|
if (Float.isNaN(y)) {
|
||||||
|
|||||||
@@ -72,26 +72,26 @@ public class HudState extends BaseAppState {
|
|||||||
protected void initialize(Application application) {
|
protected void initialize(Application application) {
|
||||||
app = (SimpleApplication) application;
|
app = (SimpleApplication) application;
|
||||||
ensureAssets();
|
ensureAssets();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void cleanup(Application application) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onEnable() {
|
||||||
hudNode = new Node("hud_bars");
|
hudNode = new Node("hud_bars");
|
||||||
|
fills[0] = fills[1] = fills[2] = null;
|
||||||
|
lastRatios[0] = lastRatios[1] = lastRatios[2] = -1f;
|
||||||
buildBars();
|
buildBars();
|
||||||
app.getGuiNode().attachChild(hudNode);
|
app.getGuiNode().attachChild(hudNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void cleanup(Application application) {
|
|
||||||
if (hudNode.getParent() != null) {
|
|
||||||
app.getGuiNode().detachChild(hudNode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onEnable() {
|
|
||||||
hudNode.setCullHint(Spatial.CullHint.Inherit);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onDisable() {
|
protected void onDisable() {
|
||||||
hudNode.setCullHint(Spatial.CullHint.Always);
|
if (hudNode != null) {
|
||||||
|
app.getGuiNode().detachChild(hudNode);
|
||||||
|
hudNode = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -0,0 +1,185 @@
|
|||||||
|
package de.blight.game.state;
|
||||||
|
|
||||||
|
import com.jme3.app.Application;
|
||||||
|
import com.jme3.app.SimpleApplication;
|
||||||
|
import com.jme3.app.state.BaseAppState;
|
||||||
|
import com.jme3.material.Material;
|
||||||
|
import com.jme3.material.RenderState;
|
||||||
|
import com.jme3.math.ColorRGBA;
|
||||||
|
import com.jme3.renderer.queue.RenderQueue;
|
||||||
|
import com.jme3.scene.Geometry;
|
||||||
|
import com.jme3.scene.Node;
|
||||||
|
import com.jme3.scene.shape.Quad;
|
||||||
|
import com.jme3.texture.Texture2D;
|
||||||
|
import com.jme3.ui.Picture;
|
||||||
|
import de.blight.game.scene.WorldScene;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zeigt Logo + Ladebalken, bis WorldScene spielbereit ist.
|
||||||
|
*
|
||||||
|
* Timing-Garantie: WorldScene.setEnabled(true) wird erst aufgerufen, nachdem
|
||||||
|
* mindestens ein vollständiger Render-Frame mit dem Overlay stattgefunden hat.
|
||||||
|
* Dazu wird app.enqueue() genutzt: Der eingereihte Task läuft zu Beginn des
|
||||||
|
* NÄCHSTEN Application.update()-Aufrufs – also nach dem aktuellen Render-Frame.
|
||||||
|
*/
|
||||||
|
public class LoadingScreenState extends BaseAppState {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(LoadingScreenState.class);
|
||||||
|
|
||||||
|
private static final float PROGRESS_SPEED = 0.18f;
|
||||||
|
private static final float TIMEOUT_SEC = 60f;
|
||||||
|
|
||||||
|
private SimpleApplication app;
|
||||||
|
private Node overlayNode;
|
||||||
|
private Geometry barFill;
|
||||||
|
|
||||||
|
private boolean readyToTrigger = false;
|
||||||
|
private boolean worldTriggered = false;
|
||||||
|
private float progress = 0f;
|
||||||
|
private float timeWaiting = 0f;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void initialize(Application application) {
|
||||||
|
app = (SimpleApplication) application;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void cleanup(Application application) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onEnable() {
|
||||||
|
if (!worldTriggered) {
|
||||||
|
readyToTrigger = false;
|
||||||
|
progress = 0f;
|
||||||
|
timeWaiting = 0f;
|
||||||
|
// Trigger erst im nächsten Frame einplanen: so wird der Overlay
|
||||||
|
// garantiert einmal gerendert, bevor WorldScene.onEnable() blockiert.
|
||||||
|
app.enqueue((java.util.concurrent.Callable<Void>) () -> {
|
||||||
|
if (!worldTriggered) {
|
||||||
|
readyToTrigger = true;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
buildOverlay();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDisable() {
|
||||||
|
if (overlayNode != null) {
|
||||||
|
app.getGuiNode().detachChild(overlayNode);
|
||||||
|
overlayNode = null;
|
||||||
|
barFill = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update(float tpf) {
|
||||||
|
if (!worldTriggered && readyToTrigger) {
|
||||||
|
readyToTrigger = false;
|
||||||
|
worldTriggered = true;
|
||||||
|
WorldScene ws = app.getStateManager().getState(WorldScene.class);
|
||||||
|
if (ws != null) {
|
||||||
|
ws.setEnabled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
timeWaiting += tpf;
|
||||||
|
WorldScene ws = app.getStateManager().getState(WorldScene.class);
|
||||||
|
boolean done = (ws != null && ws.isGameReady()) || timeWaiting >= TIMEOUT_SEC;
|
||||||
|
|
||||||
|
if (!done) {
|
||||||
|
progress = Math.min(progress + tpf * PROGRESS_SPEED, 0.9f);
|
||||||
|
} else {
|
||||||
|
progress = 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (barFill != null) {
|
||||||
|
barFill.setLocalScale(Math.max(0.001f, progress), 1f, 1f);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (done) {
|
||||||
|
setEnabled(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Aufbau ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
private void buildOverlay() {
|
||||||
|
if (overlayNode != null) {
|
||||||
|
app.getGuiNode().detachChild(overlayNode);
|
||||||
|
}
|
||||||
|
float sw = app.getCamera().getWidth();
|
||||||
|
float sh = app.getCamera().getHeight();
|
||||||
|
|
||||||
|
overlayNode = new Node("loading_overlay");
|
||||||
|
|
||||||
|
// Schwarzer Vollbild-Hintergrund (Z=200: liegt garantiert über allen HUD-Elementen)
|
||||||
|
overlayNode.attachChild(makeRect(sw, sh, ColorRGBA.Black, 200f));
|
||||||
|
|
||||||
|
// Blight-Logo
|
||||||
|
buildLogo(sw, sh);
|
||||||
|
|
||||||
|
// Ladebalken
|
||||||
|
buildBar(sw, sh);
|
||||||
|
|
||||||
|
app.getGuiNode().attachChild(overlayNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void buildLogo(float sw, float sh) {
|
||||||
|
try {
|
||||||
|
Texture2D tex = (Texture2D) app.getAssetManager().loadTexture("logo.png");
|
||||||
|
int imgW = tex.getImage().getWidth();
|
||||||
|
int imgH = tex.getImage().getHeight();
|
||||||
|
float logoW = sw * 0.40f;
|
||||||
|
float logoH = (imgH > 0 && imgW > 0) ? logoW * imgH / imgW : logoW * 0.27f;
|
||||||
|
Picture logo = new Picture("loading_logo");
|
||||||
|
logo.setTexture(app.getAssetManager(), tex, true);
|
||||||
|
logo.setWidth(logoW);
|
||||||
|
logo.setHeight(logoH);
|
||||||
|
logo.setLocalTranslation((sw - logoW) / 2f, sh * 0.42f, 202f);
|
||||||
|
logo.setQueueBucket(RenderQueue.Bucket.Gui);
|
||||||
|
overlayNode.attachChild(logo);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("[LoadingScreen] logo.png nicht ladbar: {}", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void buildBar(float sw, float sh) {
|
||||||
|
float bw = sw * 0.50f;
|
||||||
|
float bh = 16f;
|
||||||
|
float bx = (sw - bw) / 2f;
|
||||||
|
float by = sh * 0.14f;
|
||||||
|
float border = 2f;
|
||||||
|
|
||||||
|
Geometry barBorder = makeRect(bw, bh, new ColorRGBA(0.45f, 0.35f, 0.08f, 1f), 1f);
|
||||||
|
barBorder.setLocalTranslation(bx, by, 201f);
|
||||||
|
overlayNode.attachChild(barBorder);
|
||||||
|
|
||||||
|
Geometry barBg = makeRect(bw - border * 2, bh - border * 2,
|
||||||
|
new ColorRGBA(0.08f, 0.08f, 0.08f, 1f), 1f);
|
||||||
|
barBg.setLocalTranslation(bx + border, by + border, 201f);
|
||||||
|
overlayNode.attachChild(barBg);
|
||||||
|
|
||||||
|
// Füllbalken: X-Scale animiert die Breite von links nach rechts
|
||||||
|
barFill = makeRect(bw - border * 2, bh - border * 2,
|
||||||
|
new ColorRGBA(0.85f, 0.65f, 0.10f, 1f), 2f);
|
||||||
|
barFill.setLocalTranslation(bx + border, by + border, 202f);
|
||||||
|
barFill.setLocalScale(Math.max(0.001f, progress), 1f, 1f);
|
||||||
|
overlayNode.attachChild(barFill);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Geometry makeRect(float w, float h, ColorRGBA color, float z) {
|
||||||
|
Quad quad = new Quad(w, h);
|
||||||
|
Geometry g = new Geometry("loading_rect", quad);
|
||||||
|
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
|
||||||
|
mat.setColor("Color", color);
|
||||||
|
mat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
|
||||||
|
g.setMaterial(mat);
|
||||||
|
g.setQueueBucket(RenderQueue.Bucket.Gui);
|
||||||
|
g.setLocalTranslation(0, 0, z);
|
||||||
|
return g;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -172,13 +172,21 @@ public class TerrainChunkState extends BaseAppState {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void update(float tpf) {
|
public void update(float tpf) {
|
||||||
float refX, refZ;
|
Vector3f camPos = cam.getLocation();
|
||||||
|
float refX = camPos.x;
|
||||||
|
float refZ = camPos.z;
|
||||||
if (!Float.isNaN(spawnHintX)) {
|
if (!Float.isNaN(spawnHintX)) {
|
||||||
refX = spawnHintX; refZ = spawnHintZ;
|
int spawnCx = worldToChunk(spawnHintX);
|
||||||
spawnHintX = Float.NaN; spawnHintZ = Float.NaN;
|
int spawnCz = worldToChunk(spawnHintZ);
|
||||||
} else {
|
if (worldToChunk(refX) != spawnCx || worldToChunk(refZ) != spawnCz) {
|
||||||
Vector3f camPos = cam.getLocation();
|
// Kamera noch nicht am Spawn-Chunk — Hint beibehalten
|
||||||
refX = camPos.x; refZ = camPos.z;
|
refX = spawnHintX;
|
||||||
|
refZ = spawnHintZ;
|
||||||
|
} else {
|
||||||
|
// Kamera hat den Spawn-Chunk erreicht — Hint freigeben
|
||||||
|
spawnHintX = Float.NaN;
|
||||||
|
spawnHintZ = Float.NaN;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
int pcx = worldToChunk(refX);
|
int pcx = worldToChunk(refX);
|
||||||
int pcz = worldToChunk(refZ);
|
int pcz = worldToChunk(refZ);
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ menu.graphics.row.shadow=Schattenqualität
|
|||||||
menu.graphics.val.shadow.low=Niedrig
|
menu.graphics.val.shadow.low=Niedrig
|
||||||
menu.graphics.val.shadow.medium=Mittel
|
menu.graphics.val.shadow.medium=Mittel
|
||||||
menu.graphics.val.shadow.high=Hoch
|
menu.graphics.val.shadow.high=Hoch
|
||||||
|
menu.graphics.val.resolution.native=Nativ
|
||||||
menu.graphics.btn.apply=Übernehmen
|
menu.graphics.btn.apply=Übernehmen
|
||||||
menu.graphics.btn.cancel=Abbrechen
|
menu.graphics.btn.cancel=Abbrechen
|
||||||
|
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ menu.graphics.row.shadow=Shadow Quality
|
|||||||
menu.graphics.val.shadow.low=Low
|
menu.graphics.val.shadow.low=Low
|
||||||
menu.graphics.val.shadow.medium=Medium
|
menu.graphics.val.shadow.medium=Medium
|
||||||
menu.graphics.val.shadow.high=High
|
menu.graphics.val.shadow.high=High
|
||||||
|
menu.graphics.val.resolution.native=Native
|
||||||
menu.graphics.btn.apply=Apply
|
menu.graphics.btn.apply=Apply
|
||||||
menu.graphics.btn.cancel=Cancel
|
menu.graphics.btn.cancel=Cancel
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
Reference in New Issue
Block a user