Weiter gearbeitet an allem möglichen
This commit is contained in:
281
blight-game/src/main/java/de/blight/game/state/DrownState.java
Normal file
281
blight-game/src/main/java/de/blight/game/state/DrownState.java
Normal file
@@ -0,0 +1,281 @@
|
||||
package de.blight.game.state;
|
||||
|
||||
import com.jme3.app.Application;
|
||||
import com.jme3.app.SimpleApplication;
|
||||
import com.jme3.app.state.BaseAppState;
|
||||
import com.jme3.bullet.control.CharacterControl;
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.material.RenderState;
|
||||
import com.jme3.math.ColorRGBA;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.renderer.queue.RenderQueue;
|
||||
import com.jme3.scene.Geometry;
|
||||
import com.jme3.scene.shape.Quad;
|
||||
import de.blight.common.model.MainCharacter;
|
||||
import de.blight.game.control.PlayerInputControl;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Ertrink-Sequenz:
|
||||
* 1. MONITORING – Tiefe überwachen
|
||||
* 2. FADING_OUT – Tiefe > 1,5 m: Eingabe weg, Charakter läuft weiter,
|
||||
* Bild + Ton in 3 s ausblenden
|
||||
* 3. BLACK – 2 s Schwarzbild, dann Teleport + HP/Mana/Stamina=1
|
||||
* 4. WAIT_PHYSICS – 1,5 s warten damit Terrain-Physik lädt
|
||||
* 5. FADING_IN – 3 s Einblenden (REVIVE eingefroren)
|
||||
* 6. REVIVE_PLAYING – REVIVE-Animation läuft durch
|
||||
* → zurück zu MONITORING
|
||||
*/
|
||||
public class DrownState extends BaseAppState {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(DrownState.class);
|
||||
|
||||
/** Tiefe (m) die den Ertrink-Vorgang auslöst. */
|
||||
private static final float TRIGGER_DEPTH = 1.5f;
|
||||
|
||||
private static final float FADE_OUT_DUR = 3.0f;
|
||||
private static final float BLACK_DUR = 2.0f;
|
||||
private static final float WAIT_PHYSICS_DUR = 1.5f;
|
||||
private static final float FADE_IN_DUR = 3.0f;
|
||||
|
||||
// Strand-Suche
|
||||
private static final float MAX_BEACH_H = 5f;
|
||||
private static final float MAX_SLOPE_DEG = 10f;
|
||||
private static final float SCAN_STEP_R = 3f;
|
||||
private static final float SCAN_MAX_R = 300f;
|
||||
private static final int SCAN_DIRS = 16;
|
||||
|
||||
private final TerrainChunkState terrain;
|
||||
private final CharacterControl physicsChar;
|
||||
private final PlayerInputControl playerInput;
|
||||
private final MainCharacter mainCharacter;
|
||||
|
||||
private SimpleApplication app;
|
||||
private Geometry overlay;
|
||||
private Material overlayMat;
|
||||
|
||||
/** Gesetzt von WorldScene nach setupAnimationContext(). */
|
||||
private String reviveClip = null;
|
||||
private float reviveLength = 0f;
|
||||
|
||||
private enum Phase { MONITORING, FADING_OUT, BLACK, WAIT_PHYSICS, FADING_IN, REVIVE_PLAYING }
|
||||
private Phase phase = Phase.MONITORING;
|
||||
private float timer = 0f;
|
||||
|
||||
public DrownState(TerrainChunkState terrain, CharacterControl physicsChar,
|
||||
PlayerInputControl playerInput, MainCharacter mainCharacter) {
|
||||
this.terrain = terrain;
|
||||
this.physicsChar = physicsChar;
|
||||
this.playerInput = playerInput;
|
||||
this.mainCharacter = mainCharacter;
|
||||
}
|
||||
|
||||
public void setReviveInfo(String clip, float length) {
|
||||
this.reviveClip = clip;
|
||||
this.reviveLength = length;
|
||||
log.info("[DrownState] REVIVE-Info: clip='{}' {}s", clip, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initialize(Application application) {
|
||||
app = (SimpleApplication) application;
|
||||
float w = app.getCamera().getWidth();
|
||||
float h = app.getCamera().getHeight();
|
||||
overlay = new Geometry("drown_overlay", new Quad(w, h));
|
||||
overlayMat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
|
||||
overlayMat.setColor("Color", new ColorRGBA(0f, 0f, 0f, 0f));
|
||||
overlayMat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
|
||||
overlay.setMaterial(overlayMat);
|
||||
overlay.setQueueBucket(RenderQueue.Bucket.Gui);
|
||||
overlay.setLocalTranslation(0f, 0f, 50f);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void cleanup(Application application) {
|
||||
detachOverlay();
|
||||
restoreVolume();
|
||||
}
|
||||
|
||||
@Override protected void onEnable() {}
|
||||
@Override protected void onDisable() {}
|
||||
|
||||
@Override
|
||||
public void update(float tpf) {
|
||||
switch (phase) {
|
||||
case MONITORING -> updateMonitoring();
|
||||
case FADING_OUT -> updateFadingOut(tpf);
|
||||
case BLACK -> updateBlack(tpf);
|
||||
case WAIT_PHYSICS -> updateWaitPhysics(tpf);
|
||||
case FADING_IN -> updateFadingIn(tpf);
|
||||
case REVIVE_PLAYING -> updateRevivePlaying(tpf);
|
||||
}
|
||||
}
|
||||
|
||||
// ── MONITORING ────────────────────────────────────────────────────────────
|
||||
|
||||
private void updateMonitoring() {
|
||||
if (physicsChar == null) return;
|
||||
Vector3f pos = physicsChar.getPhysicsLocation();
|
||||
float depth = Math.max(0f, -terrain.getHeightAt(pos.x, pos.z));
|
||||
if (depth >= TRIGGER_DEPTH) {
|
||||
log.info("[DrownState] Tiefe {}m – Ertrink-Sequenz gestartet", depth);
|
||||
playerInput.blockInputsKeepMoving();
|
||||
attachOverlay();
|
||||
setAlpha(0f);
|
||||
setListenerScale(1f);
|
||||
timer = FADE_OUT_DUR;
|
||||
phase = Phase.FADING_OUT;
|
||||
}
|
||||
}
|
||||
|
||||
// ── FADING_OUT (3 s) ──────────────────────────────────────────────────────
|
||||
|
||||
private void updateFadingOut(float tpf) {
|
||||
timer -= tpf;
|
||||
float alpha = Math.max(0f, 1f - timer / FADE_OUT_DUR);
|
||||
setAlpha(alpha);
|
||||
setListenerScale(1f - alpha);
|
||||
if (timer <= 0f) {
|
||||
setAlpha(1f);
|
||||
setListenerScale(0f);
|
||||
timer = BLACK_DUR;
|
||||
phase = Phase.BLACK;
|
||||
}
|
||||
}
|
||||
|
||||
// ── BLACK (2 s) ───────────────────────────────────────────────────────────
|
||||
|
||||
private void updateBlack(float tpf) {
|
||||
timer -= tpf;
|
||||
if (timer <= 0f) {
|
||||
executeDrown();
|
||||
}
|
||||
}
|
||||
|
||||
private void executeDrown() {
|
||||
// Vollständig blockieren (stoppt auch Laufrichtung)
|
||||
playerInput.blockForIntro();
|
||||
|
||||
if (mainCharacter != null) {
|
||||
mainCharacter.setCurrentHp(1);
|
||||
mainCharacter.setCurrentMana(1);
|
||||
mainCharacter.setCurrentStamina(1);
|
||||
}
|
||||
|
||||
Vector3f pos = physicsChar.getPhysicsLocation();
|
||||
Vector3f beach = findNearestBeach(pos.x, pos.z);
|
||||
physicsChar.setPhysicsLocation(beach);
|
||||
playerInput.setGroundGrace(60);
|
||||
log.info("[DrownState] Teleportiert zu {}", beach);
|
||||
|
||||
timer = WAIT_PHYSICS_DUR;
|
||||
phase = Phase.WAIT_PHYSICS;
|
||||
}
|
||||
|
||||
// ── WAIT_PHYSICS (1,5 s) ──────────────────────────────────────────────────
|
||||
|
||||
private void updateWaitPhysics(float tpf) {
|
||||
timer -= tpf;
|
||||
if (timer <= 0f) {
|
||||
if (reviveClip != null) {
|
||||
playerInput.startFrozenRevive(reviveClip);
|
||||
}
|
||||
timer = FADE_IN_DUR;
|
||||
phase = Phase.FADING_IN;
|
||||
}
|
||||
}
|
||||
|
||||
// ── FADING_IN (3 s) ───────────────────────────────────────────────────────
|
||||
|
||||
private void updateFadingIn(float tpf) {
|
||||
timer -= tpf;
|
||||
float alpha = Math.max(0f, timer / FADE_IN_DUR);
|
||||
setAlpha(alpha);
|
||||
setListenerScale(1f - alpha);
|
||||
if (timer <= 0f) {
|
||||
detachOverlay();
|
||||
restoreVolume();
|
||||
if (reviveClip != null) {
|
||||
playerInput.unfreezeRevive();
|
||||
timer = reviveLength;
|
||||
phase = Phase.REVIVE_PLAYING;
|
||||
} else {
|
||||
playerInput.unblockInputs();
|
||||
phase = Phase.MONITORING;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── REVIVE_PLAYING ────────────────────────────────────────────────────────
|
||||
|
||||
private void updateRevivePlaying(float tpf) {
|
||||
timer -= tpf;
|
||||
if (timer <= 0f) {
|
||||
playerInput.unblockInputs();
|
||||
phase = Phase.MONITORING;
|
||||
log.info("[DrownState] Sequenz abgeschlossen – Spieler freigegeben");
|
||||
}
|
||||
}
|
||||
|
||||
// ── Hilfsmethoden ─────────────────────────────────────────────────────────
|
||||
|
||||
private void attachOverlay() {
|
||||
if (overlay.getParent() == null) app.getGuiNode().attachChild(overlay);
|
||||
}
|
||||
|
||||
private void detachOverlay() {
|
||||
if (overlay != null && overlay.getParent() != null) app.getGuiNode().detachChild(overlay);
|
||||
}
|
||||
|
||||
private void setAlpha(float alpha) {
|
||||
overlayMat.setColor("Color", new ColorRGBA(0f, 0f, 0f, alpha));
|
||||
}
|
||||
|
||||
private void setListenerScale(float scale) {
|
||||
AudioSettingsState as = app.getStateManager().getState(AudioSettingsState.class);
|
||||
float master = (as != null) ? as.getMaster() : 1f;
|
||||
app.getListener().setVolume(master * Math.max(0f, Math.min(1f, scale)));
|
||||
}
|
||||
|
||||
private void restoreVolume() {
|
||||
AudioSettingsState as = app.getStateManager().getState(AudioSettingsState.class);
|
||||
float master = (as != null) ? as.getMaster() : 1f;
|
||||
app.getListener().setVolume(master);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sucht radial vom Ausgangspunkt aus den nächsten flachen Landpunkt.
|
||||
* Nahe Punkte liegen im selben Terrain-Chunk → Physik ist bereits geladen.
|
||||
*/
|
||||
private Vector3f findNearestBeach(float startX, float startZ) {
|
||||
float maxSlope = (float) Math.tan(Math.toRadians(MAX_SLOPE_DEG));
|
||||
float slopeStep = 4f;
|
||||
double angleStep = 2 * Math.PI / SCAN_DIRS;
|
||||
|
||||
for (float r = SCAN_STEP_R; r <= SCAN_MAX_R; r += SCAN_STEP_R) {
|
||||
for (int d = 0; d < SCAN_DIRS; d++) {
|
||||
float x = startX + r * (float) Math.cos(d * angleStep);
|
||||
float z = startZ + r * (float) Math.sin(d * angleStep);
|
||||
|
||||
float h = terrain.getHeightAt(x, z);
|
||||
if (h < 0.2f || h > MAX_BEACH_H) continue;
|
||||
|
||||
float hxp = terrain.getHeightAt(x + slopeStep, z);
|
||||
float hxn = terrain.getHeightAt(x - slopeStep, z);
|
||||
float hzp = terrain.getHeightAt(x, z + slopeStep);
|
||||
float hzn = terrain.getHeightAt(x, z - slopeStep);
|
||||
float dhdx = (hxp - hxn) / (2f * slopeStep);
|
||||
float dhdz = (hzp - hzn) / (2f * slopeStep);
|
||||
float slope = (float) Math.sqrt(dhdx * dhdx + dhdz * dhdz);
|
||||
if (slope > maxSlope) continue;
|
||||
|
||||
log.info("[DrownState] Strandpunkt: ({}, {}), h={}, r={}m", x, z, h, r);
|
||||
return new Vector3f(x, h + 1.0f, z);
|
||||
}
|
||||
}
|
||||
|
||||
log.warn("[DrownState] Kein Strandpunkt gefunden – Fallback");
|
||||
return new Vector3f(startX, Math.max(terrain.getHeightAt(startX, startZ), 0f) + 3f, startZ);
|
||||
}
|
||||
}
|
||||
286
blight-game/src/main/java/de/blight/game/state/HudState.java
Normal file
286
blight-game/src/main/java/de/blight/game/state/HudState.java
Normal file
@@ -0,0 +1,286 @@
|
||||
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.Mesh;
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.scene.Spatial;
|
||||
import com.jme3.scene.VertexBuffer;
|
||||
import com.jme3.texture.Texture;
|
||||
import com.jme3.ui.Picture;
|
||||
import com.jme3.util.BufferUtils;
|
||||
import de.blight.common.model.MainCharacter;
|
||||
import de.blight.game.animation.AnimationLibrary;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
/**
|
||||
* HUD-Balken (unten rechts): Health (rot), Stamina (gelb), Mana (blau).
|
||||
* PNG-basiert – Dateien in Textures/hud/ können durch eigene Grafiken ersetzt werden.
|
||||
*/
|
||||
public class HudState extends BaseAppState {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(HudState.class);
|
||||
|
||||
// Dimensionen des Fülbereichs (ohne Rahmen)
|
||||
private static final float BAR_W = 200f;
|
||||
private static final float BAR_H = 18f;
|
||||
private static final float BORDER = 2f;
|
||||
private static final float MARGIN = 12f;
|
||||
private static final float GAP = 5f;
|
||||
|
||||
private static final float SLOT_W = BAR_W + BORDER * 2;
|
||||
private static final float SLOT_H = BAR_H + BORDER * 2;
|
||||
|
||||
private static final String[] FILL_ASSETS = {
|
||||
"Textures/hud/bar_fill_health.png",
|
||||
"Textures/hud/bar_fill_stamina.png",
|
||||
"Textures/hud/bar_fill_mana.png",
|
||||
};
|
||||
|
||||
private static final ColorRGBA[] FILL_COLORS = {
|
||||
new ColorRGBA(0.85f, 0.10f, 0.10f, 1f),
|
||||
new ColorRGBA(0.90f, 0.80f, 0.10f, 1f),
|
||||
new ColorRGBA(0.10f, 0.30f, 0.90f, 1f),
|
||||
};
|
||||
|
||||
private final MainCharacter mc;
|
||||
|
||||
private SimpleApplication app;
|
||||
private Node hudNode;
|
||||
private final Geometry[] fills = new Geometry[3];
|
||||
private final float[] lastRatios = { -1f, -1f, -1f };
|
||||
|
||||
public HudState(MainCharacter mc) {
|
||||
this.mc = mc;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initialize(Application application) {
|
||||
app = (SimpleApplication) application;
|
||||
ensureAssets();
|
||||
hudNode = new Node("hud_bars");
|
||||
buildBars();
|
||||
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
|
||||
protected void onDisable() {
|
||||
hudNode.setCullHint(Spatial.CullHint.Always);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(float tpf) {
|
||||
if (mc == null) return;
|
||||
int maxHp = mc.getMaxHp();
|
||||
int maxSt = mc.getMaxStamina();
|
||||
int maxMn = mc.getMaxMana();
|
||||
applyRatio(0, maxHp > 0 ? (float) mc.getCurrentHp() / maxHp : 0f);
|
||||
applyRatio(1, maxSt > 0 ? (float) mc.getCurrentStamina() / maxSt : 0f);
|
||||
applyRatio(2, maxMn > 0 ? (float) mc.getCurrentMana() / maxMn : 0f);
|
||||
}
|
||||
|
||||
// ── Aufbau ───────────────────────────────────────────────────────────────────
|
||||
|
||||
private void buildBars() {
|
||||
float screenW = app.getCamera().getWidth();
|
||||
|
||||
// Stapelung von unten: idx=2 (mana) ganz unten, idx=0 (health) ganz oben
|
||||
for (int i = 0; i < 3; i++) {
|
||||
int stackPos = 2 - i; // health → stack 2 (höchste Y), mana → stack 0
|
||||
float x = screenW - MARGIN - SLOT_W;
|
||||
float y = MARGIN + stackPos * (SLOT_H + GAP);
|
||||
buildBar(i, x, y);
|
||||
}
|
||||
}
|
||||
|
||||
private void buildBar(int idx, float x, float y) {
|
||||
// Rahmen (Frame) – transparent innen, Rand sichtbar
|
||||
Picture frame = loadPicture("Textures/hud/bar_frame.png", SLOT_W, SLOT_H,
|
||||
new ColorRGBA(0.65f, 0.65f, 0.65f, 1f));
|
||||
frame.setLocalTranslation(x, y, 1f);
|
||||
hudNode.attachChild(frame);
|
||||
|
||||
// Hintergrund (schwarz)
|
||||
Picture bg = loadPicture("Textures/hud/bar_bg.png", BAR_W, BAR_H, ColorRGBA.Black);
|
||||
bg.setLocalTranslation(x + BORDER, y + BORDER, 2f);
|
||||
hudNode.attachChild(bg);
|
||||
|
||||
// Füllbalken (UV-geclippt, kein Strecken des Textur-Gradienten)
|
||||
Geometry fill = buildFillGeometry(idx, x + BORDER, y + BORDER, 3f);
|
||||
fills[idx] = fill;
|
||||
hudNode.attachChild(fill);
|
||||
}
|
||||
|
||||
private Geometry buildFillGeometry(int idx, float x, float y, float z) {
|
||||
Mesh mesh = new Mesh();
|
||||
fillMesh(mesh, BAR_W, 1f); // initial: voll
|
||||
|
||||
Material mat;
|
||||
try {
|
||||
Texture tex = app.getAssetManager().loadTexture(FILL_ASSETS[idx]);
|
||||
tex.setWrap(Texture.WrapMode.EdgeClamp);
|
||||
mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
|
||||
mat.setTexture("ColorMap", tex);
|
||||
} catch (Exception e) {
|
||||
mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
|
||||
mat.setColor("Color", FILL_COLORS[idx]);
|
||||
}
|
||||
mat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
|
||||
|
||||
Geometry g = new Geometry("hud_fill_" + idx, mesh);
|
||||
g.setMaterial(mat);
|
||||
g.setQueueBucket(RenderQueue.Bucket.Gui);
|
||||
g.setLocalTranslation(x, y, z);
|
||||
return g;
|
||||
}
|
||||
|
||||
/** Setzt Breite und UV-Clipping des Fill-Meshes auf den gegebenen Ratio (0–1). */
|
||||
private void applyRatio(int idx, float ratio) {
|
||||
ratio = Math.max(0f, Math.min(1f, ratio));
|
||||
if (Math.abs(ratio - lastRatios[idx]) < 0.001f) return;
|
||||
lastRatios[idx] = ratio;
|
||||
|
||||
Geometry g = fills[idx];
|
||||
float w = BAR_W * ratio;
|
||||
|
||||
Mesh mesh = g.getMesh();
|
||||
FloatBuffer pos = (FloatBuffer) mesh.getBuffer(VertexBuffer.Type.Position).getData();
|
||||
pos.put(3, w); // Vertex 1: x
|
||||
pos.put(6, w); // Vertex 2: x
|
||||
mesh.getBuffer(VertexBuffer.Type.Position).setUpdateNeeded();
|
||||
|
||||
// UV-Clipping: nur ratio-Anteil der Textur sichtbar (kein Strecken)
|
||||
FloatBuffer uv = (FloatBuffer) mesh.getBuffer(VertexBuffer.Type.TexCoord).getData();
|
||||
uv.put(2, ratio); // UV 1: u
|
||||
uv.put(4, ratio); // UV 2: u
|
||||
mesh.getBuffer(VertexBuffer.Type.TexCoord).setUpdateNeeded();
|
||||
|
||||
mesh.updateBound();
|
||||
}
|
||||
|
||||
/** Erstellt ein Quad-Mesh mit UV-Koordinaten die bei maxU enden (für Clipping). */
|
||||
private static void fillMesh(Mesh mesh, float w, float maxU) {
|
||||
float h = BAR_H;
|
||||
mesh.setBuffer(VertexBuffer.Type.Position, 3,
|
||||
BufferUtils.createFloatBuffer(
|
||||
0, 0, 0,
|
||||
w, 0, 0,
|
||||
w, h, 0,
|
||||
0, h, 0
|
||||
));
|
||||
mesh.setBuffer(VertexBuffer.Type.TexCoord, 2,
|
||||
BufferUtils.createFloatBuffer(
|
||||
0f, 0f,
|
||||
maxU, 0f,
|
||||
maxU, 1f,
|
||||
0f, 1f
|
||||
));
|
||||
mesh.setBuffer(VertexBuffer.Type.Index, 3,
|
||||
BufferUtils.createShortBuffer((short)0,(short)1,(short)2,(short)0,(short)2,(short)3));
|
||||
mesh.setMode(Mesh.Mode.Triangles);
|
||||
mesh.updateBound();
|
||||
}
|
||||
|
||||
// ── PNG-Loader ────────────────────────────────────────────────────────────────
|
||||
|
||||
private Picture loadPicture(String assetPath, float w, float h, ColorRGBA fallback) {
|
||||
Picture p = new Picture("hud_" + assetPath);
|
||||
try {
|
||||
p.setImage(app.getAssetManager(), assetPath, true);
|
||||
} catch (Exception e) {
|
||||
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
|
||||
mat.setColor("Color", fallback);
|
||||
mat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
|
||||
p.setMaterial(mat);
|
||||
}
|
||||
p.setWidth(w);
|
||||
p.setHeight(h);
|
||||
p.setQueueBucket(RenderQueue.Bucket.Gui);
|
||||
return p;
|
||||
}
|
||||
|
||||
// ── Placeholder-PNGs erzeugen ─────────────────────────────────────────────────
|
||||
|
||||
private void ensureAssets() {
|
||||
Path root = AnimationLibrary.findAssetRoot();
|
||||
Path dir = root.resolve("Textures").resolve("hud");
|
||||
try {
|
||||
Files.createDirectories(dir);
|
||||
} catch (IOException e) {
|
||||
log.warn("[HUD] Verzeichnis nicht erstellbar: {}", dir);
|
||||
return;
|
||||
}
|
||||
|
||||
// Rahmen: grauer Rand, transparente Mitte
|
||||
ensureFrame(dir.resolve("bar_frame.png"), (int) SLOT_W, (int) SLOT_H, (int) BORDER,
|
||||
180, 180, 180);
|
||||
// Hintergrund: schwarz
|
||||
ensureSolid(dir.resolve("bar_bg.png"), 4, 4, 0, 0, 0, 255);
|
||||
// Füllfarben (kleine Kacheln, werden skaliert/geclippt)
|
||||
ensureSolid(dir.resolve("bar_fill_health.png"), 4, 4, 217, 25, 25, 255);
|
||||
ensureSolid(dir.resolve("bar_fill_stamina.png"), 4, 4, 229, 204, 25, 255);
|
||||
ensureSolid(dir.resolve("bar_fill_mana.png"), 4, 4, 25, 76, 229, 255);
|
||||
}
|
||||
|
||||
private static void ensureSolid(Path path, int w, int h,
|
||||
int r, int g, int b, int a) {
|
||||
if (Files.exists(path)) return;
|
||||
try {
|
||||
BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
|
||||
int argb = (a << 24) | (r << 16) | (g << 8) | b;
|
||||
for (int py = 0; py < h; py++) {
|
||||
for (int px = 0; px < w; px++) {
|
||||
img.setRGB(px, py, argb);
|
||||
}
|
||||
}
|
||||
ImageIO.write(img, "PNG", path.toFile());
|
||||
} catch (Exception e) {
|
||||
log.warn("[HUD] PNG nicht erstellbar: {}", path);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ensureFrame(Path path, int w, int h, int border,
|
||||
int r, int g, int b) {
|
||||
if (Files.exists(path)) return;
|
||||
try {
|
||||
BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
|
||||
int borderArgb = (255 << 24) | (r << 16) | (g << 8) | b;
|
||||
for (int py = 0; py < h; py++) {
|
||||
for (int px = 0; px < w; px++) {
|
||||
boolean isBorder = px < border || py < border
|
||||
|| px >= w - border || py >= h - border;
|
||||
img.setRGB(px, py, isBorder ? borderArgb : 0);
|
||||
}
|
||||
}
|
||||
ImageIO.write(img, "PNG", path.toFile());
|
||||
} catch (Exception e) {
|
||||
log.warn("[HUD] Frame-PNG nicht erstellbar: {}", path);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user