Weitere Bugfixes und kleine Feature Erweiterungen
This commit is contained in:
@@ -339,12 +339,13 @@ public class BlightGame extends SimpleApplication {
|
||||
super.initialize(sm, a);
|
||||
setDisplayFps(false);
|
||||
setDisplayStatView(false);
|
||||
int h = a.getCamera().getHeight();
|
||||
if (fpsText != null) fpsText.setLocalTranslation(0, h, 0);
|
||||
if (statsView != null) {
|
||||
float fpsH = fpsText != null ? fpsText.getLineHeight() : 0f;
|
||||
statsView.setLocalTranslation(0, h - fpsH, 0);
|
||||
}
|
||||
int h = a.getCamera().getHeight();
|
||||
float fpsH = fpsText != null ? fpsText.getLineHeight() : 0f;
|
||||
float statsH = statsView != null ? statsView.getHeight() : 0f;
|
||||
if (fpsText != null) fpsText.setLocalTranslation(0, h, 0);
|
||||
if (statsView != null) statsView.setLocalTranslation(0, h - fpsH - statsH, 0);
|
||||
if (darkenFps != null) darkenFps.setLocalTranslation(0, h - fpsH, -1);
|
||||
if (darkenStats != null) darkenStats.setLocalTranslation(0, h - fpsH - statsH, -1);
|
||||
}
|
||||
};
|
||||
stateManager.attach(statsState);
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
package de.blight.game;
|
||||
|
||||
import com.jme3.asset.AssetInfo;
|
||||
import com.jme3.asset.AssetKey;
|
||||
import com.jme3.asset.AssetLocator;
|
||||
import com.jme3.asset.AssetManager;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Leitet veraltete Textur-Pfade auf die aktuellen Speicherorte um.
|
||||
* Behebt Laden von j3o-Dateien, die noch den alten Textur-Pfad
|
||||
* (z. B. Textures/internal/leaves/) statt des neuen (internal/foliage/) enthalten.
|
||||
*
|
||||
* Registriert mit root = blight-assets/src/main/resources.
|
||||
*/
|
||||
public class LegacyAssetRedirectLocator implements AssetLocator {
|
||||
|
||||
private static final Map<String, String> PREFIXES = Map.of(
|
||||
"Textures/bark/", "Textures/internal/bark/",
|
||||
"Textures/leaves/", "Textures/internal/foliage/",
|
||||
"Textures/internal/leaves/", "Textures/internal/foliage/",
|
||||
"Textures/fern/", "Textures/internal/fern/",
|
||||
"Textures/water/", "Textures/internal/water/",
|
||||
"Textures/Water/", "Textures/internal/water/"
|
||||
);
|
||||
|
||||
private Path root;
|
||||
|
||||
@Override
|
||||
public void setRootPath(String rootPath) {
|
||||
root = Paths.get(rootPath);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("rawtypes")
|
||||
public AssetInfo locate(AssetManager manager, AssetKey key) {
|
||||
String name = key.getName();
|
||||
for (var entry : PREFIXES.entrySet()) {
|
||||
if (name.startsWith(entry.getKey())) {
|
||||
String remapped = entry.getValue() + name.substring(entry.getKey().length());
|
||||
AssetInfo info = tryFile(manager, key, remapped);
|
||||
if (info != null) return info;
|
||||
if (remapped.toLowerCase().endsWith(".tga")) {
|
||||
info = tryFile(manager, key, remapped.substring(0, remapped.length() - 4) + ".png");
|
||||
if (info != null) return info;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
private AssetInfo tryFile(AssetManager manager, AssetKey key, String rel) {
|
||||
Path p = root.resolve(rel);
|
||||
if (!Files.exists(p)) return null;
|
||||
return new AssetInfo(manager, key) {
|
||||
@Override
|
||||
public InputStream openStream() {
|
||||
try { return new FileInputStream(p.toFile()); }
|
||||
catch (FileNotFoundException ex) { throw new RuntimeException(ex); }
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -46,6 +46,10 @@ public abstract class MenuScreen extends BaseAppState {
|
||||
public static final float CONT_W = MenuCanvas.CONT_W;
|
||||
public static final float CONT_H = MenuCanvas.CONT_H;
|
||||
|
||||
private static int openCount = 0;
|
||||
|
||||
public static boolean isAnyOpen() { return openCount > 0; }
|
||||
|
||||
public enum BtnStyle { DEFAULT, SAVE, QUIT, DISABLED, ARROW }
|
||||
|
||||
protected SimpleApplication app;
|
||||
@@ -77,6 +81,7 @@ public abstract class MenuScreen extends BaseAppState {
|
||||
|
||||
@Override
|
||||
protected void onEnable() {
|
||||
openCount++;
|
||||
uiScale = MenuCanvas.getScale(app.getCamera());
|
||||
bgLayer = buildBgLayer();
|
||||
canvasNode = buildCanvas();
|
||||
@@ -98,6 +103,7 @@ public abstract class MenuScreen extends BaseAppState {
|
||||
|
||||
@Override
|
||||
protected void onDisable() {
|
||||
if (openCount > 0) { openCount--; }
|
||||
onDisableExtras();
|
||||
app.getInputManager().removeListener(clickListener);
|
||||
if (app.getInputManager().hasMapping(MAP_CLICK)) {
|
||||
|
||||
@@ -40,6 +40,8 @@ public abstract class OverlayState extends BaseAppState {
|
||||
|
||||
private static OverlayState activeOverlay;
|
||||
|
||||
public static boolean isAnyOpen() { return activeOverlay != null; }
|
||||
|
||||
// ── Panel-Dimensionen (90% des Referenzraums) ─────────────────────────────
|
||||
|
||||
public static final float PW = MenuCanvas.CONT_W;
|
||||
|
||||
@@ -232,9 +232,9 @@ public class WorldScene extends BaseAppState {
|
||||
@Override
|
||||
protected void onEnable() {
|
||||
try {
|
||||
assetManager.registerLocator(
|
||||
AnimationLibrary.findAssetRoot().toAbsolutePath().toString(),
|
||||
com.jme3.asset.plugins.FileLocator.class);
|
||||
String assetRootStr = AnimationLibrary.findAssetRoot().toAbsolutePath().toString();
|
||||
assetManager.registerLocator(assetRootStr, com.jme3.asset.plugins.FileLocator.class);
|
||||
assetManager.registerLocator(assetRootStr, de.blight.game.LegacyAssetRedirectLocator.class);
|
||||
} catch (Exception ignored) {}
|
||||
|
||||
app.getCamera().setFrustumPerspective(45f,
|
||||
@@ -271,7 +271,7 @@ public class WorldScene extends BaseAppState {
|
||||
// Bullet-Charakter: Kapsel 0.4 Radius, 1.0 Höhe, Y-Achse (1)
|
||||
CapsuleCollisionShape capsule = new CapsuleCollisionShape(0.4f, 1.0f, 1);
|
||||
physicsChar = new CharacterControl(capsule, 0.05f);
|
||||
physicsChar.setJumpSpeed(12f);
|
||||
physicsChar.setJumpSpeed(7f);
|
||||
physicsChar.setFallSpeed(35f);
|
||||
physicsChar.setGravity(35f);
|
||||
character.addControl(physicsChar);
|
||||
|
||||
@@ -4,8 +4,6 @@ import com.jme3.app.Application;
|
||||
import com.jme3.app.SimpleApplication;
|
||||
import com.jme3.app.state.BaseAppState;
|
||||
import com.jme3.asset.AssetManager;
|
||||
import com.jme3.font.BitmapFont;
|
||||
import com.jme3.font.BitmapText;
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.material.RenderState;
|
||||
import com.jme3.math.ColorRGBA;
|
||||
@@ -15,51 +13,48 @@ import com.jme3.math.Vector3f;
|
||||
import com.jme3.renderer.Camera;
|
||||
import com.jme3.renderer.queue.RenderQueue;
|
||||
import com.jme3.scene.Geometry;
|
||||
import com.jme3.scene.Mesh;
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.scene.VertexBuffer;
|
||||
import com.jme3.util.BufferUtils;
|
||||
import com.jme3.scene.Spatial;
|
||||
import com.jme3.scene.shape.Quad;
|
||||
import com.jme3.texture.Texture;
|
||||
import de.blight.game.animation.AnimationLibrary;
|
||||
import de.blight.game.config.MenuScreen;
|
||||
import de.blight.game.config.OverlayState;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
/**
|
||||
* Kompass unten links – runde rotierende Rose, identisch mit dem Editor-Kompass.
|
||||
* Textur-basierter Kompass unten links.
|
||||
*
|
||||
* Die Rose dreht sich mit dem Kamera-Yaw; die Buchstaben konter-rotieren,
|
||||
* damit sie immer aufrecht bleiben. Ein festes goldenes Dreieck oben zeigt
|
||||
* die aktuelle Blickrichtung an.
|
||||
* compass_rose.png (100×100) – dreht sich mit dem Kamera-Yaw.
|
||||
* compass_needle.png (100×100) – fix, zeigt immer nach oben (= aktuelle Blickrichtung).
|
||||
*
|
||||
* Beide Dateien werden beim ersten Start als Platzhalter erzeugt
|
||||
* und können durch eigene Grafiken ersetzt werden.
|
||||
*/
|
||||
public class CompassHudState extends BaseAppState {
|
||||
|
||||
// ── Layout ────────────────────────────────────────────────────────────────
|
||||
private static final float SIZE = 100f; // Canvas-Größe (px)
|
||||
private static final float RADIUS = 47f; // Kreis-Außenradius
|
||||
private static final float LABEL_R = 30f; // Beschriftungsradius vom Mittelpunkt
|
||||
private static final float MARGIN = 16f; // Abstand zum Bildschirmrand
|
||||
private static final Logger log = LoggerFactory.getLogger(CompassHudState.class);
|
||||
|
||||
// ── Farben (matching EditorApp.drawCompass) ───────────────────────────────
|
||||
private static final ColorRGBA COL_BG = new ColorRGBA(0.047f, 0.047f, 0.094f, 0.82f);
|
||||
private static final ColorRGBA COL_BORDER = new ColorRGBA(0.392f, 0.392f, 0.627f, 0.85f);
|
||||
private static final ColorRGBA COL_TICK_MJ = new ColorRGBA(0.627f, 0.627f, 0.784f, 0.65f);
|
||||
private static final ColorRGBA COL_TICK_MN = new ColorRGBA(0.627f, 0.627f, 0.784f, 0.45f);
|
||||
private static final ColorRGBA COL_N = new ColorRGBA(1.00f, 0.314f, 0.314f, 1.00f);
|
||||
private static final ColorRGBA COL_CARD = new ColorRGBA(0.824f, 0.824f, 0.902f, 1.00f);
|
||||
private static final ColorRGBA COL_MARKER = new ColorRGBA(1.00f, 0.863f, 0.235f, 0.95f);
|
||||
private static final ColorRGBA COL_CENTER = new ColorRGBA(0.784f, 0.784f, 0.941f, 0.85f);
|
||||
|
||||
// Kompasswinkel (Grad von Nord CW): N=0, O=90, S=180, W=270
|
||||
private static final String[] LABELS = {"N", "O", "S", "W"};
|
||||
private static final float[] LABEL_DEG = {0f, 90f, 180f, 270f};
|
||||
private static final float SIZE = 100f;
|
||||
private static final float MARGIN = 16f;
|
||||
|
||||
private SimpleApplication app;
|
||||
private Camera cam;
|
||||
private AssetManager assets;
|
||||
private BitmapFont font;
|
||||
|
||||
private Node compassNode;
|
||||
private Node roseNode;
|
||||
private Node compassNode;
|
||||
private Node roseNode;
|
||||
|
||||
private final Node[] labelNodes = new Node[4];
|
||||
private final Quaternion roseRot = new Quaternion();
|
||||
private final Quaternion labelRot = new Quaternion();
|
||||
private final Quaternion roseRot = new Quaternion();
|
||||
private boolean lastMenuOpen = false;
|
||||
|
||||
// ── Lifecycle ─────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -68,7 +63,7 @@ public class CompassHudState extends BaseAppState {
|
||||
app = (SimpleApplication) application;
|
||||
cam = app.getCamera();
|
||||
assets = app.getAssetManager();
|
||||
font = assets.loadFont("Interface/Fonts/Default.fnt");
|
||||
ensureAssets();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -94,169 +89,160 @@ public class CompassHudState extends BaseAppState {
|
||||
public void update(float tpf) {
|
||||
if (compassNode == null) { return; }
|
||||
|
||||
Vector3f dir = cam.getDirection();
|
||||
// yaw=0 = Blick nach -Z (Nord); JME3-Kamera blickt in -Z bei Norden
|
||||
float yaw = FastMath.atan2(dir.x, -dir.z);
|
||||
boolean menuOpen = OverlayState.isAnyOpen() || MenuScreen.isAnyOpen();
|
||||
if (menuOpen != lastMenuOpen) {
|
||||
lastMenuOpen = menuOpen;
|
||||
compassNode.setCullHint(menuOpen ? Spatial.CullHint.Always : Spatial.CullHint.Inherit);
|
||||
}
|
||||
if (menuOpen) { return; }
|
||||
|
||||
// Rose dreht sich so, dass Nord immer in der echten Nord-Richtung liegt
|
||||
Vector3f dir = cam.getDirection();
|
||||
float yaw = FastMath.atan2(dir.x, -dir.z);
|
||||
roseRot.fromAngleAxis(-yaw, Vector3f.UNIT_Z);
|
||||
roseNode.setLocalRotation(roseRot);
|
||||
|
||||
// Buchstaben konter-rotieren → bleiben immer aufrecht
|
||||
labelRot.fromAngleAxis(yaw, Vector3f.UNIT_Z);
|
||||
for (Node ln : labelNodes) {
|
||||
ln.setLocalRotation(labelRot);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Aufbau ────────────────────────────────────────────────────────────────
|
||||
|
||||
private void buildCompass() {
|
||||
float oy = HotbarState.MARGIN_BOT + HotbarState.SLOT_SIZE + 8f;
|
||||
float cx = MARGIN + SIZE / 2f;
|
||||
float cy = oy + SIZE / 2f;
|
||||
// Textur-Größen auslesen → echte Pixelgröße bestimmt Layout und Quad-Größe
|
||||
float roseW = SIZE, roseH = SIZE;
|
||||
try {
|
||||
com.jme3.texture.Texture roseTex = assets.loadTexture("Textures/hud/compass_rose.png");
|
||||
roseW = roseTex.getImage().getWidth();
|
||||
roseH = roseTex.getImage().getHeight();
|
||||
} catch (Exception ignored) {}
|
||||
|
||||
// compassNode-Mittelpunkt so setzen, dass die Rose mit MARGIN Abstand zur Ecke endet
|
||||
float cx = MARGIN + roseW / 2f;
|
||||
float cy = MARGIN + roseH / 2f;
|
||||
|
||||
compassNode = new Node("compass");
|
||||
compassNode.setLocalTranslation(cx, cy, 0f);
|
||||
|
||||
// Rahmen (etwas größerer Kreis) + Hintergrund
|
||||
compassNode.attachChild(makeCircle(RADIUS + 1.5f, 64, COL_BORDER, 1f));
|
||||
compassNode.attachChild(makeCircle(RADIUS, 64, COL_BG, 2f));
|
||||
|
||||
// Rotierende Rose
|
||||
roseNode = new Node("rose");
|
||||
compassNode.attachChild(roseNode);
|
||||
roseNode.attachChild(makeTexQuad("Textures/hud/compass_rose.png", 2f));
|
||||
|
||||
// 8 Tick-Striche (major = Hauptrichtungen, minor = Zwischenrichtungen)
|
||||
for (int i = 0; i < 8; i++) {
|
||||
float deg = i * 45f;
|
||||
boolean maj = (i % 2 == 0);
|
||||
float innerR = RADIUS - (maj ? 9f : 5f);
|
||||
roseNode.attachChild(makeTickQuad(deg, innerR, maj ? COL_TICK_MJ : COL_TICK_MN, 3f));
|
||||
}
|
||||
|
||||
// Himmelsrichtungs-Beschriftungen
|
||||
for (int i = 0; i < 4; i++) {
|
||||
float ca = LABEL_DEG[i] * FastMath.DEG_TO_RAD;
|
||||
float lx = FastMath.sin(ca) * LABEL_R;
|
||||
float ly = FastMath.cos(ca) * LABEL_R;
|
||||
|
||||
BitmapText lbl = makeBitmapText(LABELS[i], i == 0 ? 14 : 12, i == 0 ? COL_N : COL_CARD);
|
||||
// Buchstabe am Label-Ursprung zentrieren
|
||||
lbl.setLocalTranslation(-lbl.getLineWidth() * 0.5f, lbl.getLineHeight() * 0.4f, 0f);
|
||||
|
||||
Node ln = new Node("lbl_" + LABELS[i]);
|
||||
ln.setLocalTranslation(lx, ly, 4f);
|
||||
ln.attachChild(lbl);
|
||||
labelNodes[i] = ln;
|
||||
roseNode.attachChild(ln);
|
||||
}
|
||||
|
||||
// Fixer Richtungs-Zeiger: gelbes Dreieck oben (nicht in roseNode)
|
||||
compassNode.attachChild(makeTriangle(COL_MARKER, 5f));
|
||||
|
||||
// Mittelpunkt-Punkt
|
||||
compassNode.attachChild(makeCircle(2.5f, 16, COL_CENTER, 6f));
|
||||
// Nadel zentriert auf dem Kompass-Mittelpunkt, auf natürlicher Textur-Größe
|
||||
compassNode.attachChild(makeTexQuad("Textures/hud/compass_needle.png", 5f));
|
||||
|
||||
app.getGuiNode().attachChild(compassNode);
|
||||
}
|
||||
|
||||
// ── Geometry-Helfer ───────────────────────────────────────────────────────
|
||||
|
||||
/** Gefüllter Kreis (Triangle-Fan) zentriert bei (0,0). */
|
||||
private Geometry makeCircle(float r, int segs, ColorRGBA col, float z) {
|
||||
float[] verts = new float[(segs + 2) * 3];
|
||||
// Mittelpunkt
|
||||
verts[0] = 0f; verts[1] = 0f; verts[2] = 0f;
|
||||
for (int i = 0; i <= segs; i++) {
|
||||
float a = FastMath.TWO_PI * i / segs;
|
||||
int base = (i + 1) * 3;
|
||||
verts[base] = FastMath.cos(a) * r;
|
||||
verts[base + 1] = FastMath.sin(a) * r;
|
||||
verts[base + 2] = 0f;
|
||||
/** Lädt Textur, erzeugt Quad in natürlicher Textur-Größe, zentriert bei (0,0). */
|
||||
private Geometry makeTexQuad(String assetPath, float z) {
|
||||
Material mat = new Material(assets, "Common/MatDefs/Misc/Unshaded.j3md");
|
||||
mat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
|
||||
float w = SIZE, h = SIZE;
|
||||
try {
|
||||
Texture tex = assets.loadTexture(assetPath);
|
||||
w = tex.getImage().getWidth();
|
||||
h = tex.getImage().getHeight();
|
||||
mat.setTexture("ColorMap", tex);
|
||||
} catch (Exception e) {
|
||||
mat.setColor("Color", new ColorRGBA(1f, 1f, 1f, 0.5f));
|
||||
}
|
||||
int[] idx = new int[segs * 3];
|
||||
for (int i = 0; i < segs; i++) {
|
||||
idx[i * 3] = 0;
|
||||
idx[i * 3 + 1] = i + 1;
|
||||
idx[i * 3 + 2] = i + 2;
|
||||
}
|
||||
Geometry g = new Geometry("circle", buildMesh(verts, idx));
|
||||
g.setMaterial(makeMat(col));
|
||||
g.setLocalTranslation(0f, 0f, z);
|
||||
g.setQueueBucket(col.a < 1f ? RenderQueue.Bucket.Transparent : RenderQueue.Bucket.Gui);
|
||||
String name = "cmp_" + assetPath.substring(assetPath.lastIndexOf('/') + 1);
|
||||
Geometry g = new Geometry(name, new Quad(w, h));
|
||||
g.setLocalTranslation(-w / 2f, -h / 2f, z);
|
||||
g.setMaterial(mat);
|
||||
g.setQueueBucket(RenderQueue.Bucket.Gui);
|
||||
return g;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tick-Strich als dünnes Quad entlang der radialen Richtung.
|
||||
* ca_deg: Kompasswinkel (0=Nord, 90=Ost, CW).
|
||||
*/
|
||||
private Geometry makeTickQuad(float ca_deg, float innerR, ColorRGBA col, float z) {
|
||||
float ca = ca_deg * FastMath.DEG_TO_RAD;
|
||||
float sx = FastMath.sin(ca); // Richtung entlang des Radius
|
||||
float cy_ = FastMath.cos(ca);
|
||||
float px = -cy_ * 0.75f; // Halbe Breite (senkrecht zur Radiale)
|
||||
float py = sx * 0.75f;
|
||||
// ── Placeholder-PNGs ──────────────────────────────────────────────────────
|
||||
|
||||
float[] v = {
|
||||
sx * innerR + px, cy_ * innerR + py, z, // v0 links innen
|
||||
sx * innerR - px, cy_ * innerR - py, z, // v1 rechts innen
|
||||
sx * RADIUS + px, cy_ * RADIUS + py, z, // v2 links außen
|
||||
sx * RADIUS - px, cy_ * RADIUS - py, z // v3 rechts außen
|
||||
};
|
||||
int[] idx = {0, 1, 2, 1, 3, 2};
|
||||
|
||||
Geometry g = new Geometry("tick", buildMesh(v, idx));
|
||||
g.setMaterial(makeMat(col));
|
||||
g.setQueueBucket(RenderQueue.Bucket.Transparent);
|
||||
return g;
|
||||
}
|
||||
|
||||
/**
|
||||
* Festes gelbes Dreieck – zeigt immer nach oben (Blickrichtung).
|
||||
* Spitze bei (0, RADIUS−5), Basis bei (±5, RADIUS−16).
|
||||
*/
|
||||
private Geometry makeTriangle(ColorRGBA col, float z) {
|
||||
float tipY = RADIUS - 5f;
|
||||
float baseY = RADIUS - 16f;
|
||||
float[] v = {
|
||||
0f, tipY, 0f, // Spitze
|
||||
-5f, baseY, 0f, // Basis links
|
||||
5f, baseY, 0f // Basis rechts
|
||||
};
|
||||
// CCW von +Z-Seite: (0,high)→(-5,low)→(5,low) ✓
|
||||
int[] idx = {0, 1, 2};
|
||||
Geometry g = new Geometry("triangle", buildMesh(v, idx));
|
||||
g.setMaterial(makeMat(col));
|
||||
g.setLocalTranslation(0f, 0f, z);
|
||||
g.setQueueBucket(RenderQueue.Bucket.Transparent);
|
||||
return g;
|
||||
}
|
||||
|
||||
private static Mesh buildMesh(float[] verts, int[] idx) {
|
||||
Mesh m = new Mesh();
|
||||
m.setBuffer(VertexBuffer.Type.Position, 3, BufferUtils.createFloatBuffer(verts));
|
||||
m.setBuffer(VertexBuffer.Type.Index, 3, BufferUtils.createIntBuffer(idx));
|
||||
m.updateBound();
|
||||
return m;
|
||||
}
|
||||
|
||||
private Material makeMat(ColorRGBA col) {
|
||||
Material m = new Material(assets, "Common/MatDefs/Misc/Unshaded.j3md");
|
||||
m.setColor("Color", col.clone());
|
||||
if (col.a < 1f) {
|
||||
m.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
|
||||
private void ensureAssets() {
|
||||
Path root = AnimationLibrary.findAssetRoot();
|
||||
Path dir = root.resolve("Textures").resolve("hud");
|
||||
try {
|
||||
Files.createDirectories(dir);
|
||||
} catch (IOException e) {
|
||||
log.warn("[Compass] Verzeichnis nicht erstellbar: {}", dir);
|
||||
return;
|
||||
}
|
||||
return m;
|
||||
ensureRose(dir.resolve("compass_rose.png"));
|
||||
ensureNeedle(dir.resolve("compass_needle.png"));
|
||||
}
|
||||
|
||||
private BitmapText makeBitmapText(String text, int size, ColorRGBA col) {
|
||||
BitmapText t = new BitmapText(font);
|
||||
t.setSize(size);
|
||||
t.setColor(col.clone());
|
||||
t.setText(text);
|
||||
t.setQueueBucket(RenderQueue.Bucket.Gui);
|
||||
return t;
|
||||
/** Erzeugt Platzhalter-Rose: dunkler Kreis + 8 Striche + N/E/S/W. */
|
||||
private static void ensureRose(Path path) {
|
||||
if (Files.exists(path)) { return; }
|
||||
try {
|
||||
int cx = 50, cy = 50, r = 47;
|
||||
BufferedImage img = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB);
|
||||
Graphics2D g = img.createGraphics();
|
||||
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
|
||||
|
||||
// Hintergrund-Kreis
|
||||
g.setColor(new Color(12, 12, 24, 209));
|
||||
g.fillOval(cx - r, cy - r, r * 2, r * 2);
|
||||
|
||||
// Rand-Ring
|
||||
g.setColor(new Color(100, 100, 160, 216));
|
||||
g.setStroke(new BasicStroke(1.5f));
|
||||
g.drawOval(cx - r, cy - r, r * 2, r * 2);
|
||||
|
||||
// 8 Tick-Striche; angle −90° → 0° zeigt nach oben (= Norden in BufferedImage-Y)
|
||||
for (int i = 0; i < 8; i++) {
|
||||
double angle = Math.toRadians(i * 45.0 - 90.0);
|
||||
boolean major = (i % 2 == 0);
|
||||
int innerR = r - (major ? 9 : 5);
|
||||
int x1 = (int)(cx + Math.cos(angle) * innerR);
|
||||
int y1 = (int)(cy + Math.sin(angle) * innerR);
|
||||
int x2 = (int)(cx + Math.cos(angle) * (r - 1));
|
||||
int y2 = (int)(cy + Math.sin(angle) * (r - 1));
|
||||
g.setColor(new Color(160, 160, 200, 166));
|
||||
g.setStroke(new BasicStroke(major ? 1.5f : 1.0f));
|
||||
g.drawLine(x1, y1, x2, y2);
|
||||
}
|
||||
|
||||
// Himmelsrichtungs-Buchstaben
|
||||
int labelR = 31;
|
||||
|
||||
g.setFont(new Font("SansSerif", Font.BOLD, 13));
|
||||
FontMetrics fm = g.getFontMetrics();
|
||||
g.setColor(new Color(255, 80, 80, 255));
|
||||
drawCentered(g, fm, "N", cx, cy - labelR); // oben in BufferedImage = oben auf dem Bildschirm
|
||||
|
||||
g.setFont(new Font("SansSerif", Font.BOLD, 11));
|
||||
fm = g.getFontMetrics();
|
||||
g.setColor(new Color(210, 210, 230, 255));
|
||||
drawCentered(g, fm, "E", cx + labelR, cy);
|
||||
drawCentered(g, fm, "S", cx, cy + labelR);
|
||||
drawCentered(g, fm, "W", cx - labelR, cy);
|
||||
|
||||
g.dispose();
|
||||
ImageIO.write(img, "PNG", path.toFile());
|
||||
} catch (Exception e) {
|
||||
log.warn("[Compass] compass_rose.png nicht erstellbar: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/** Erzeugt Platzhalter-Nadel: gelbes Dreieck, Spitze oben. */
|
||||
private static void ensureNeedle(Path path) {
|
||||
if (Files.exists(path)) { return; }
|
||||
try {
|
||||
BufferedImage img = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB);
|
||||
Graphics2D g = img.createGraphics();
|
||||
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
g.setColor(new Color(255, 220, 60, 242));
|
||||
// Spitze bei y=8, Basis bei y=21; kleines y in BufferedImage = oben auf dem Bildschirm
|
||||
int[] xp = {50, 43, 57};
|
||||
int[] yp = { 8, 21, 21};
|
||||
g.fillPolygon(xp, yp, 3);
|
||||
g.dispose();
|
||||
ImageIO.write(img, "PNG", path.toFile());
|
||||
} catch (Exception e) {
|
||||
log.warn("[Compass] compass_needle.png nicht erstellbar: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static void drawCentered(Graphics2D g, FontMetrics fm, String text, int cx, int cy) {
|
||||
int x = cx - fm.stringWidth(text) / 2;
|
||||
int y = cy + (fm.getAscent() - fm.getDescent()) / 2;
|
||||
g.drawString(text, x, y);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ import com.jme3.app.Application;
|
||||
import com.jme3.app.SimpleApplication;
|
||||
import com.jme3.app.state.BaseAppState;
|
||||
import com.jme3.asset.AssetManager;
|
||||
import com.jme3.bullet.BulletAppState;
|
||||
import com.jme3.bullet.collision.PhysicsRayTestResult;
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.material.RenderState;
|
||||
import com.jme3.math.*;
|
||||
@@ -201,6 +203,8 @@ public class GrassState extends BaseAppState
|
||||
List<GrassTuft> tufts = chunkTufts[idx];
|
||||
if (tufts.isEmpty()) return;
|
||||
|
||||
BulletAppState bullet = getApplication().getStateManager().getState(BulletAppState.class);
|
||||
|
||||
Map<Integer, List<float[]>> bySlot = new LinkedHashMap<>();
|
||||
for (GrassTuft t : tufts) {
|
||||
long seed = (long) Float.floatToRawIntBits(t.x()) * 0x9E3779B9L
|
||||
@@ -210,7 +214,7 @@ public class GrassState extends BaseAppState
|
||||
for (int b = 0; b < BLADES_PER_TUFT; b++) {
|
||||
float bx = t.x() + (rng.nextFloat() - 0.5f) * TUFT_SPREAD * 2f;
|
||||
float bz = t.z() + (rng.nextFloat() - 0.5f) * TUFT_SPREAD * 2f;
|
||||
float th = terrainChunkState.getHeightAt(bx, bz);
|
||||
float th = surfaceHeight(bullet, bx, bz);
|
||||
if (Float.isNaN(th)) continue;
|
||||
float h = t.height() * (0.7f + rng.nextFloat() * 0.6f);
|
||||
blades.add(new float[]{bx, th, bz, h});
|
||||
@@ -259,12 +263,36 @@ public class GrassState extends BaseAppState
|
||||
chunkNodes[ci].setCullHint(visible ? Spatial.CullHint.Inherit : Spatial.CullHint.Always);
|
||||
}
|
||||
|
||||
// ── Oberflächen-Höhe via Physik-Raycast ──────────────────────────────────
|
||||
|
||||
private float surfaceHeight(BulletAppState bullet, float wx, float wz) {
|
||||
float base = terrainChunkState.getHeightAt(wx, wz);
|
||||
if (bullet == null || bullet.getPhysicsSpace() == null) {
|
||||
return base;
|
||||
}
|
||||
float refY = Float.isNaN(base) ? 0f : base;
|
||||
Vector3f from = new Vector3f(wx, refY + 200f, wz);
|
||||
Vector3f to = new Vector3f(wx, refY - 5f, wz);
|
||||
List<PhysicsRayTestResult> hits = bullet.getPhysicsSpace().rayTest(from, to);
|
||||
if (hits.isEmpty()) {
|
||||
return base;
|
||||
}
|
||||
float bestFrac = Float.MAX_VALUE;
|
||||
for (PhysicsRayTestResult hit : hits) {
|
||||
if (hit.getHitFraction() < bestFrac) {
|
||||
bestFrac = hit.getHitFraction();
|
||||
}
|
||||
}
|
||||
return from.y + (to.y - from.y) * bestFrac;
|
||||
}
|
||||
|
||||
// ── Mesh: Kreuz-Quad mit UV ───────────────────────────────────────────────
|
||||
|
||||
private static Mesh buildGrassMesh(List<float[]> blades) {
|
||||
int n = blades.size();
|
||||
FloatBuffer pos = BufferUtils.createFloatBuffer(n * 8 * 3);
|
||||
FloatBuffer uv = BufferUtils.createFloatBuffer(n * 8 * 2);
|
||||
FloatBuffer nrm = BufferUtils.createFloatBuffer(n * 8 * 3);
|
||||
IntBuffer idx = BufferUtils.createIntBuffer(n * 12);
|
||||
|
||||
int vi = 0;
|
||||
@@ -272,15 +300,15 @@ public class GrassState extends BaseAppState
|
||||
float x = blade[0], y = blade[1], z = blade[2], h = blade[3];
|
||||
float w = Math.max(0.05f, h * BLADE_WIDTH);
|
||||
|
||||
pos.put(x-w).put(y ).put(z); uv.put(0).put(0);
|
||||
pos.put(x+w).put(y ).put(z); uv.put(1).put(0);
|
||||
pos.put(x+w).put(y+h).put(z); uv.put(1).put(1);
|
||||
pos.put(x-w).put(y+h).put(z); uv.put(0).put(1);
|
||||
pos.put(x-w).put(y ).put(z); uv.put(0).put(0); nrm.put(0).put(1).put(0);
|
||||
pos.put(x+w).put(y ).put(z); uv.put(1).put(0); nrm.put(0).put(1).put(0);
|
||||
pos.put(x+w).put(y+h).put(z); uv.put(1).put(1); nrm.put(0).put(1).put(0);
|
||||
pos.put(x-w).put(y+h).put(z); uv.put(0).put(1); nrm.put(0).put(1).put(0);
|
||||
|
||||
pos.put(x).put(y ).put(z-w); uv.put(0).put(0);
|
||||
pos.put(x).put(y ).put(z+w); uv.put(1).put(0);
|
||||
pos.put(x).put(y+h).put(z+w); uv.put(1).put(1);
|
||||
pos.put(x).put(y+h).put(z-w); uv.put(0).put(1);
|
||||
pos.put(x).put(y ).put(z-w); uv.put(0).put(0); nrm.put(0).put(1).put(0);
|
||||
pos.put(x).put(y ).put(z+w); uv.put(1).put(0); nrm.put(0).put(1).put(0);
|
||||
pos.put(x).put(y+h).put(z+w); uv.put(1).put(1); nrm.put(0).put(1).put(0);
|
||||
pos.put(x).put(y+h).put(z-w); uv.put(0).put(1); nrm.put(0).put(1).put(0);
|
||||
|
||||
idx.put(vi ).put(vi+1).put(vi+2);
|
||||
idx.put(vi ).put(vi+2).put(vi+3);
|
||||
@@ -292,6 +320,7 @@ public class GrassState extends BaseAppState
|
||||
Mesh mesh = new Mesh();
|
||||
mesh.setBuffer(VertexBuffer.Type.Position, 3, pos);
|
||||
mesh.setBuffer(VertexBuffer.Type.TexCoord, 2, uv);
|
||||
mesh.setBuffer(VertexBuffer.Type.Normal, 3, nrm);
|
||||
mesh.setBuffer(VertexBuffer.Type.Index, 3, idx);
|
||||
mesh.updateBound();
|
||||
return mesh;
|
||||
|
||||
@@ -21,6 +21,8 @@ import com.jme3.scene.shape.Quad;
|
||||
import com.jme3.texture.Texture;
|
||||
import de.blight.common.model.Item;
|
||||
import de.blight.common.model.MainCharacter;
|
||||
import de.blight.game.config.MenuScreen;
|
||||
import de.blight.game.config.OverlayState;
|
||||
|
||||
/**
|
||||
* Immer sichtbare Schnellzugriffsleiste (Hotbar) mit 10 Slots (Tasten 1–9, 0).
|
||||
@@ -60,8 +62,9 @@ public class HotbarState extends BaseAppState {
|
||||
private Node hotbarNode;
|
||||
private float originX, originY;
|
||||
|
||||
final Item[] slots = new Item[SLOT_COUNT];
|
||||
private int activeSlot = 0;
|
||||
final Item[] slots = new Item[SLOT_COUNT];
|
||||
private int activeSlot = 0;
|
||||
private boolean lastMenuOpen = false;
|
||||
|
||||
private final Geometry[] borderGeoms = new Geometry[SLOT_COUNT];
|
||||
private final Node[] thumbNodes = new Node[SLOT_COUNT];
|
||||
@@ -98,6 +101,17 @@ public class HotbarState extends BaseAppState {
|
||||
@Override
|
||||
protected void cleanup(Application application) {}
|
||||
|
||||
@Override
|
||||
public void update(float tpf) {
|
||||
boolean menuOpen = OverlayState.isAnyOpen() || MenuScreen.isAnyOpen();
|
||||
if (menuOpen != lastMenuOpen) {
|
||||
lastMenuOpen = menuOpen;
|
||||
if (hotbarNode != null) {
|
||||
hotbarNode.setCullHint(menuOpen ? Spatial.CullHint.Always : Spatial.CullHint.Inherit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Aufbau ───────────────────────────────────────────────────────────────
|
||||
private void buildHotbar() {
|
||||
float sw = app.getCamera().getWidth();
|
||||
|
||||
@@ -17,6 +17,8 @@ import com.jme3.ui.Picture;
|
||||
import com.jme3.util.BufferUtils;
|
||||
import de.blight.common.model.MainCharacter;
|
||||
import de.blight.game.animation.AnimationLibrary;
|
||||
import de.blight.game.config.MenuScreen;
|
||||
import de.blight.game.config.OverlayState;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -62,7 +64,8 @@ public class HudState extends BaseAppState {
|
||||
private SimpleApplication app;
|
||||
private Node hudNode;
|
||||
private final Geometry[] fills = new Geometry[3];
|
||||
private final float[] lastRatios = { -1f, -1f, -1f };
|
||||
private final float[] lastRatios = { -1f, -1f, -1f };
|
||||
private boolean lastMenuOpen = false;
|
||||
|
||||
public HudState(MainCharacter mc) {
|
||||
this.mc = mc;
|
||||
@@ -96,6 +99,13 @@ public class HudState extends BaseAppState {
|
||||
|
||||
@Override
|
||||
public void update(float tpf) {
|
||||
boolean menuOpen = OverlayState.isAnyOpen() || MenuScreen.isAnyOpen();
|
||||
if (menuOpen != lastMenuOpen) {
|
||||
lastMenuOpen = menuOpen;
|
||||
if (hudNode != null) {
|
||||
hudNode.setCullHint(menuOpen ? Spatial.CullHint.Always : Spatial.CullHint.Inherit);
|
||||
}
|
||||
}
|
||||
if (mc == null) return;
|
||||
int maxHp = mc.getMaxHp();
|
||||
int maxSt = mc.getMaxStamina();
|
||||
|
||||
@@ -74,6 +74,8 @@ public class SculptedMeshState extends BaseAppState {
|
||||
ColorRGBA ac = dns.getCustomAmbient();
|
||||
material.setVector3("SunColor", new Vector3f(sc.r, sc.g, sc.b));
|
||||
material.setVector3("AmbientColor", new Vector3f(ac.r, ac.g, ac.b));
|
||||
RainState rs = app.getStateManager().getState(RainState.class);
|
||||
if (rs != null) material.setFloat("Wetness", rs.getWetness());
|
||||
}
|
||||
|
||||
// ── Laden ────────────────────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user