Mehrere Probleme behoben

This commit is contained in:
2026-08-16 21:48:19 +02:00
parent 225d9cfa82
commit 13a1c45ca6
1132 changed files with 754 additions and 133 deletions

View File

@@ -28,6 +28,7 @@ import de.blight.game.state.DrownState;
import de.blight.game.state.HotbarState;
import de.blight.game.state.HudState;
import de.blight.game.state.LoadingScreenState;
import de.blight.game.state.SplashScreenState;
import javax.imageio.ImageIO;
import java.nio.IntBuffer;
@@ -56,6 +57,7 @@ public class BlightGame extends SimpleApplication {
private PauseMenu pauseMenu;
private MainMenuState mainMenuState;
private LoadingScreenState loadingScreen;
private SplashScreenState splashScreen;
/** Routed durch onClose-Callbacks, damit Config/Grafik-Screen zurück zum richtigen Menü springen. */
private Runnable screenCloseTarget;
@@ -445,7 +447,8 @@ public class BlightGame extends SimpleApplication {
if (autostart) {
startWorld();
} else {
showMainMenu();
splashScreen = new SplashScreenState(this::showMainMenu);
stateManager.attach(splashScreen);
}
}

View File

@@ -189,10 +189,10 @@ public class WorldScene extends BaseAppState {
return result;
}
/** Liefert die Wassertiefe an (worldX, worldZ) in Metern; 0 wenn an Land. */
/** Liefert die Wassertiefe des Spielers in Metern; 0 wenn über Y=0. */
public float queryWaterDepth(float worldX, float worldZ) {
if (terrainChunkState == null) return 0f;
return Math.max(0f, -terrainChunkState.getHeightAt(worldX, worldZ));
if (physicsChar == null) return 0f;
return Math.max(0f, -physicsChar.getPhysicsLocation().y);
}
/** Wird von ConfigScreen nach dem Speichern aufgerufen. */

View File

@@ -130,7 +130,7 @@ public class DrownState extends BaseAppState {
private void updateMonitoring() {
if (physicsChar == null) return;
Vector3f pos = physicsChar.getPhysicsLocation();
float depth = Math.max(0f, -terrain.getHeightAt(pos.x, pos.z));
float depth = Math.max(0f, -pos.y);
if (depth >= TRIGGER_DEPTH) {
log.info("[DrownState] Tiefe {}m Ertrink-Sequenz gestartet", depth);
playerInput.blockInputsKeepMoving();

View File

@@ -0,0 +1,211 @@
package de.blight.game.state;
import com.jme3.app.Application;
import com.jme3.app.SimpleApplication;
import com.jme3.app.state.BaseAppState;
import com.jme3.input.RawInputListener;
import com.jme3.input.event.*;
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 org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SplashScreenState extends BaseAppState {
private static final Logger log = LoggerFactory.getLogger(SplashScreenState.class);
private static final float FADE_DURATION = 1f;
private static final float HOLD_DURATION = 5f;
private static final float BLACK_DURATION = 1f;
private static final String JME_LOGO_PATH = "jme_logo.png";
private enum Phase { BLACK, FADE_IN, HOLD, FADE_OUT }
private final Runnable onComplete;
private SimpleApplication app;
private Node overlayNode;
private Geometry fadeOverlay;
private int currentLogo = 0;
private Phase phase = Phase.BLACK;
private float timer = 0f;
private boolean skipRequested = false;
private final RawInputListener skipListener = new RawInputListener() {
@Override public void onKeyEvent(KeyInputEvent evt) { if (evt.isPressed()) skipRequested = true; }
@Override public void onMouseButtonEvent(MouseButtonEvent evt) { if (evt.isPressed()) skipRequested = true; }
@Override public void beginInput() {}
@Override public void endInput() {}
@Override public void onMouseMotionEvent(MouseMotionEvent evt) {}
@Override public void onTouchEvent(TouchEvent evt) {}
@Override public void onJoyAxisEvent(JoyAxisEvent evt) {}
@Override public void onJoyButtonEvent(JoyButtonEvent evt) {}
};
public SplashScreenState(Runnable onComplete) {
this.onComplete = onComplete;
}
@Override
protected void initialize(Application application) {
app = (SimpleApplication) application;
}
@Override protected void cleanup(Application application) {}
@Override
protected void onEnable() {
currentLogo = 0;
phase = Phase.BLACK;
timer = 0f;
skipRequested = false;
app.getInputManager().addRawInputListener(skipListener);
buildOverlay(0);
}
@Override
protected void onDisable() {
app.getInputManager().removeRawInputListener(skipListener);
clearOverlay();
}
@Override
public void update(float tpf) {
if (fadeOverlay == null) return;
timer += tpf;
if (skipRequested) {
skipRequested = false;
if (phase == Phase.BLACK || phase == Phase.FADE_IN || phase == Phase.HOLD) {
phase = Phase.FADE_OUT;
timer = 0f;
} else {
advanceLogo();
return;
}
}
switch (phase) {
case BLACK -> {
setFadeAlpha(1f);
if (timer >= BLACK_DURATION) { phase = Phase.FADE_IN; timer = 0f; }
}
case FADE_IN -> {
setFadeAlpha(1f - Math.min(timer / FADE_DURATION, 1f));
if (timer >= FADE_DURATION) { phase = Phase.HOLD; timer = 0f; }
}
case HOLD -> {
setFadeAlpha(0f);
if (timer >= HOLD_DURATION) { phase = Phase.FADE_OUT; timer = 0f; }
}
case FADE_OUT -> {
setFadeAlpha(Math.min(timer / FADE_DURATION, 1f));
if (timer >= FADE_DURATION) { advanceLogo(); }
}
}
}
private void advanceLogo() {
currentLogo++;
if (currentLogo >= 2) {
setEnabled(false);
onComplete.run();
} else {
clearOverlay();
buildOverlay(currentLogo);
phase = Phase.FADE_IN;
timer = 0f;
}
}
private void clearOverlay() {
if (overlayNode != null) {
app.getGuiNode().detachChild(overlayNode);
overlayNode = null;
fadeOverlay = null;
}
}
private void buildOverlay(int logoIndex) {
float sw = app.getCamera().getWidth();
float sh = app.getCamera().getHeight();
overlayNode = new Node("splash_overlay");
overlayNode.attachChild(makeRect(sw, sh, ColorRGBA.Black, 300f));
if (logoIndex == 0) {
buildOaaContent(sw, sh);
} else {
buildJmeContent(sw, sh);
}
// Black fade-overlay on top: alpha 1 = content hidden, alpha 0 = content fully visible
fadeOverlay = makeRect(sw, sh, new ColorRGBA(0f, 0f, 0f, 1f), 305f);
overlayNode.attachChild(fadeOverlay);
app.getGuiNode().attachChild(overlayNode);
}
private void buildOaaContent(float sw, float sh) {
overlayNode.attachChild(makeRect(sw, sh, ColorRGBA.White, 301f));
try {
Texture2D tex = (Texture2D) app.getAssetManager().loadTexture("logo_oaa.png");
int imgW = tex.getImage().getWidth();
int imgH = tex.getImage().getHeight();
float logW = sw * 0.45f;
float logH = (imgW > 0) ? logW * imgH / imgW : logW * 0.5f;
Picture logo = new Picture("oaa_logo");
logo.setTexture(app.getAssetManager(), tex, true);
logo.setWidth(logW);
logo.setHeight(logH);
logo.setLocalTranslation((sw - logW) / 2f, (sh - logH) / 2f, 302f);
logo.setQueueBucket(RenderQueue.Bucket.Gui);
overlayNode.attachChild(logo);
} catch (Exception e) {
log.warn("[SplashScreen] OAA-Logo nicht ladbar: {}", e.getMessage());
}
}
private void buildJmeContent(float sw, float sh) {
overlayNode.attachChild(makeRect(sw, sh, ColorRGBA.White, 301f));
try {
Texture2D tex = (Texture2D) app.getAssetManager().loadTexture(JME_LOGO_PATH);
int imgW = tex.getImage().getWidth();
int imgH = tex.getImage().getHeight();
float logW = sw * 0.45f;
float logH = (imgW > 0) ? logW * imgH / imgW : logW * 0.5f;
Picture logo = new Picture("jme_logo");
logo.setTexture(app.getAssetManager(), tex, true);
logo.setWidth(logW);
logo.setHeight(logH);
logo.setLocalTranslation((sw - logW) / 2f, (sh - logH) / 2f, 302f);
logo.setQueueBucket(RenderQueue.Bucket.Gui);
overlayNode.attachChild(logo);
} catch (Exception e) {
log.warn("[SplashScreen] JME-Logo nicht ladbar: {}", e.getMessage());
}
}
private void setFadeAlpha(float alpha) {
fadeOverlay.getMaterial().setColor("Color", new ColorRGBA(0f, 0f, 0f, alpha));
}
private Geometry makeRect(float w, float h, ColorRGBA color, float z) {
Geometry g = new Geometry("splash_rect", new Quad(w, h));
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(0f, 0f, z);
return g;
}
}

View File

@@ -51,8 +51,9 @@ public class TerrainChunkState extends BaseAppState {
// ── Konstanten ────────────────────────────────────────────────────────────
private static final int N = ChunkTerrainIO.CHUNKS_PER_AXIS; // 32
private static final int TOTAL = ChunkTerrainIO.CHUNK_COUNT; // 1024
private static final int N = ChunkTerrainIO.CHUNKS_PER_AXIS; // 16
private static final int TOTAL = ChunkTerrainIO.CHUNK_COUNT; // 256
private static final float WORLD_HALF = ChunkTerrainIO.WORLD_SIZE / 2f; // 1024
// ── Eingabe ───────────────────────────────────────────────────────────────
@@ -261,8 +262,8 @@ public class TerrainChunkState extends BaseAppState {
float[] h = chunkHeights[ChunkTerrainIO.chunkIndex(cx, cz)];
if (h == null) return 1f;
float originX = -2048f + cx * ChunkTerrainIO.CHUNK_SIZE;
float originZ = -2048f + cz * ChunkTerrainIO.CHUNK_SIZE;
float originX = -WORLD_HALF + cx * ChunkTerrainIO.CHUNK_SIZE;
float originZ = -WORLD_HALF + cz * ChunkTerrainIO.CHUNK_SIZE;
float fx = Math.max(0, Math.min(ChunkTerrainIO.CHUNK_SIZE, worldX - originX));
float fz = Math.max(0, Math.min(ChunkTerrainIO.CHUNK_SIZE, worldZ - originZ));
@@ -296,8 +297,8 @@ public class TerrainChunkState extends BaseAppState {
// Knoten anlegen oder altes Mesh entfernen
if (chunkNodes[ci] == null) {
float nx = -2048f + cx * ChunkTerrainIO.CHUNK_SIZE + 64f;
float nz = -2048f + cz * ChunkTerrainIO.CHUNK_SIZE + 64f;
float nx = -WORLD_HALF + cx * ChunkTerrainIO.CHUNK_SIZE + 64f;
float nz = -WORLD_HALF + cz * ChunkTerrainIO.CHUNK_SIZE + 64f;
Node node = new Node("tn_" + cx + "_" + cz);
node.setLocalTranslation(nx, 0f, nz);
chunkNodes[ci] = node;
@@ -346,8 +347,8 @@ public class TerrainChunkState extends BaseAppState {
}
private static Mesh buildMesh(int cx, int cz, float[] lodH, int verts, float spacing) {
float chunkCX = -2048f + cx * ChunkTerrainIO.CHUNK_SIZE + 64f;
float chunkCZ = -2048f + cz * ChunkTerrainIO.CHUNK_SIZE + 64f;
float chunkCX = -WORLD_HALF + cx * ChunkTerrainIO.CHUNK_SIZE + 64f;
float chunkCZ = -WORLD_HALF + cz * ChunkTerrainIO.CHUNK_SIZE + 64f;
float half = (verts - 1) * spacing * 0.5f; // immer 64 m
int vertCount = verts * verts;
@@ -392,12 +393,13 @@ public class TerrainChunkState extends BaseAppState {
int gi = vi * 4;
tangents[gi] = tx; tangents[gi+1] = ty; tangents[gi+2] = tz; tangents[gi+3] = 1f;
// Welt-Raum UV für Splat-Map: JME3-TerrainQuad-Konvention V=0 → worldZ=+2048
// Welt-Raum UV für Splat-Map: V=0 → worldZ=+WORLD_HALF
float worldX = chunkCX + lx;
float worldZ = chunkCZ + lz;
float worldSize = (float) ChunkTerrainIO.WORLD_SIZE;
int ti = vi * 2;
texCoords[ti] = (worldX + 2048f) / 4096f;
texCoords[ti+1] = 1f - (worldZ + 2048f) / 4096f;
texCoords[ti] = (worldX + WORLD_HALF) / worldSize;
texCoords[ti+1] = 1f - (worldZ + WORLD_HALF) / worldSize;
}
}
@@ -470,7 +472,7 @@ public class TerrainChunkState extends BaseAppState {
// ── Hilfsmethoden ────────────────────────────────────────────────────────
private static int worldToChunk(float world) {
return Math.max(0, Math.min(N - 1, (int) ((world + 2048f) / ChunkTerrainIO.CHUNK_SIZE)));
return Math.max(0, Math.min(N - 1, (int) ((world + WORLD_HALF) / ChunkTerrainIO.CHUNK_SIZE)));
}
private static float[] flatChunk() {

Binary file not shown.

After

Width:  |  Height:  |  Size: 288 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 336 KiB