Compare commits
5 Commits
6bb195da90
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| f189e6d81f | |||
| 238776cfde | |||
| 60741d3116 | |||
| 93a3c266e1 | |||
| d19538bb63 |
Binary file not shown.
|
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 4.6 KiB |
@@ -125,12 +125,12 @@
|
|||||||
"TWO_HAND_BLOCK_STRAFE_LEFT": "Great Sword Strafe left",
|
"TWO_HAND_BLOCK_STRAFE_LEFT": "Great Sword Strafe left",
|
||||||
"TWO_HAND_BLOCK_STRAFE_RIGHT": "Great Sword Strafe Right",
|
"TWO_HAND_BLOCK_STRAFE_RIGHT": "Great Sword Strafe Right",
|
||||||
"TWO_HAND_JUMP": "Great Sword Jump",
|
"TWO_HAND_JUMP": "Great Sword Jump",
|
||||||
"TWO_HAND_RUNNING_JUMP": "Great Sword Running Jump",
|
|
||||||
"TWO_HAND_IDLE_BORED": "Great Sword Idle boring",
|
"TWO_HAND_IDLE_BORED": "Great Sword Idle boring",
|
||||||
"TWO_HAND_IDLE_BORED_2": "Great Sword Idle boring 2",
|
"TWO_HAND_IDLE_BORED_2": "Great Sword Idle boring 2",
|
||||||
"TWO_HAND_IDLE_BORED_3": "Great Sword Idle boring 3",
|
"TWO_HAND_IDLE_BORED_3": "Great Sword Idle boring 3",
|
||||||
"TWO_HAND_DEATH": "Sword And Shield Death",
|
"TWO_HAND_DEATH": "Sword And Shield Death",
|
||||||
"TWO_HAND_DEATH_2": "Sword And Shield Death 2"
|
"TWO_HAND_DEATH_2": "Sword And Shield Death 2",
|
||||||
|
"TWO_HAND_RUNNING_JUMP": "Great Sword Running Jump"
|
||||||
},
|
},
|
||||||
"previewModelPath": "Characters/Models/hero.j3o",
|
"previewModelPath": "Characters/Models/hero.j3o",
|
||||||
"animOffsets": {
|
"animOffsets": {
|
||||||
|
|||||||
Binary file not shown.
@@ -1,4 +1,4 @@
|
|||||||
#Mon Oct 12 14:17:44 CEST 2026
|
#Mon Oct 19 14:39:04 CEST 2026
|
||||||
attachedEmitters.count=0
|
attachedEmitters.count=0
|
||||||
attachedLights.count=0
|
attachedLights.count=0
|
||||||
attachedSounds.count=0
|
attachedSounds.count=0
|
||||||
|
|||||||
Binary file not shown.
@@ -1,4 +1,4 @@
|
|||||||
#Sun Sep 20 15:31:32 CEST 2026
|
#Mon Oct 19 14:39:11 CEST 2026
|
||||||
attachedEmitters.count=0
|
attachedEmitters.count=0
|
||||||
attachedLights.count=0
|
attachedLights.count=0
|
||||||
attachedSounds.count=0
|
attachedSounds.count=0
|
||||||
|
|||||||
@@ -12,13 +12,13 @@
|
|||||||
"damage": 50,
|
"damage": 50,
|
||||||
"grip": {
|
"grip": {
|
||||||
"posX": 0.10248406,
|
"posX": 0.10248406,
|
||||||
"posY": 0.104730606,
|
"posY": 0.10473058,
|
||||||
"posZ": 0.4630257,
|
"posZ": 0.4630257,
|
||||||
"rotX": 0.0,
|
"rotX": 0.0,
|
||||||
"rotY": 0.0,
|
"rotY": 0.0,
|
||||||
"rotZ": 0.0,
|
"rotZ": 0.0,
|
||||||
"grip2PosX": 0.0024840608,
|
"grip2PosX": 0.0024840608,
|
||||||
"grip2PosY": 0.104730606,
|
"grip2PosY": 0.10473058,
|
||||||
"grip2PosZ": 0.6630257,
|
"grip2PosZ": 0.6630257,
|
||||||
"handOffsetX": 0.0,
|
"handOffsetX": 0.0,
|
||||||
"handOffsetY": 0.0,
|
"handOffsetY": 0.0,
|
||||||
|
|||||||
@@ -11,12 +11,12 @@
|
|||||||
"worthGold": 0,
|
"worthGold": 0,
|
||||||
"damage": 0,
|
"damage": 0,
|
||||||
"grip": {
|
"grip": {
|
||||||
"posX": 0.37,
|
"posX": 0.1114975,
|
||||||
"posY": 0.08,
|
"posY": 0.07795838,
|
||||||
"posZ": 0.0,
|
"posZ": 0.36503196,
|
||||||
"rotX": 0.0,
|
"rotX": 0.0,
|
||||||
"rotY": -90.0,
|
"rotY": 0.0,
|
||||||
"rotZ": 90.0,
|
"rotZ": 0.0,
|
||||||
"grip2PosX": 0.0,
|
"grip2PosX": 0.0,
|
||||||
"grip2PosY": 0.0,
|
"grip2PosY": 0.0,
|
||||||
"grip2PosZ": 0.0,
|
"grip2PosZ": 0.0,
|
||||||
|
|||||||
8
blight-assets/src/main/resources/shield_attachment.json
Normal file
8
blight-assets/src/main/resources/shield_attachment.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"SHIELD": {
|
||||||
|
"rotX": -90.0,
|
||||||
|
"rotY": 0.0,
|
||||||
|
"rotZ": 0.0,
|
||||||
|
"elbowOffset": 0.25
|
||||||
|
}
|
||||||
|
}
|
||||||
18
blight-assets/src/main/resources/weapon_attachment.json
Normal file
18
blight-assets/src/main/resources/weapon_attachment.json
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"SWORD": {
|
||||||
|
"handOffX": 0.085,
|
||||||
|
"handOffY": 0.115,
|
||||||
|
"handOffZ": -0.095,
|
||||||
|
"hand2OffX": 0.0,
|
||||||
|
"hand2OffY": 0.0,
|
||||||
|
"hand2OffZ": 0.0
|
||||||
|
},
|
||||||
|
"TWO_HANDED_SWORD": {
|
||||||
|
"handOffX": 0.08,
|
||||||
|
"handOffY": 0.15,
|
||||||
|
"handOffZ": -0.09,
|
||||||
|
"hand2OffX": -0.005,
|
||||||
|
"hand2OffY": -0.04,
|
||||||
|
"hand2OffZ": 0.065
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
package de.blight.common.model;
|
||||||
|
|
||||||
|
import com.google.gson.*;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lädt und speichert per-Kategorie Schild-Attachment-Konfiguration (shield_attachment.json).
|
||||||
|
* Siehe WeaponAttachmentIO: liest vom Dateisystem (Dev-Umgebung) und prüft bei jedem Zugriff
|
||||||
|
* die mtime der Datei, damit Editor-Änderungen ohne Neustart ankommen.
|
||||||
|
*/
|
||||||
|
public final class ShieldAttachmentIO {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(ShieldAttachmentIO.class);
|
||||||
|
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
|
||||||
|
private static final String CLASSPATH_FILE = "shield_attachment.json";
|
||||||
|
|
||||||
|
private static final String[] ASSET_BASES = {
|
||||||
|
"blight-assets/src/main/resources",
|
||||||
|
"../blight-assets/src/main/resources",
|
||||||
|
"assets",
|
||||||
|
".",
|
||||||
|
};
|
||||||
|
|
||||||
|
private static final Map<String, ShieldCategoryAttachment> CONFIGS = new HashMap<>();
|
||||||
|
private static boolean loaded = false;
|
||||||
|
private static Path cachedFile = null;
|
||||||
|
private static long cachedMtime = -1;
|
||||||
|
|
||||||
|
private ShieldAttachmentIO() {}
|
||||||
|
|
||||||
|
public static ShieldCategoryAttachment get(ItemSubCategory cat) {
|
||||||
|
ensureLoaded();
|
||||||
|
if (cat == null) return new ShieldCategoryAttachment();
|
||||||
|
return CONFIGS.getOrDefault(cat.name(), new ShieldCategoryAttachment());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void set(ItemSubCategory cat, ShieldCategoryAttachment cfg) {
|
||||||
|
if (cat != null) CONFIGS.put(cat.name(), cfg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static synchronized void loadFromClasspath() {
|
||||||
|
try (InputStream is = ShieldAttachmentIO.class.getClassLoader()
|
||||||
|
.getResourceAsStream(CLASSPATH_FILE)) {
|
||||||
|
if (is == null) {
|
||||||
|
log.warn("[ShieldAttachmentIO] {} nicht auf Classpath – Defaults verwendet", CLASSPATH_FILE);
|
||||||
|
loaded = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
parse(new String(is.readAllBytes(), StandardCharsets.UTF_8));
|
||||||
|
loaded = true;
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.warn("[ShieldAttachmentIO] Ladefehler: {}", e.getMessage());
|
||||||
|
loaded = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void load(Path file) {
|
||||||
|
if (!Files.exists(file)) return;
|
||||||
|
try {
|
||||||
|
parse(Files.readString(file, StandardCharsets.UTF_8));
|
||||||
|
loaded = true;
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.warn("[ShieldAttachmentIO] Ladefehler: {}", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void save(Path file) {
|
||||||
|
try {
|
||||||
|
Files.writeString(file, GSON.toJson(CONFIGS), StandardCharsets.UTF_8);
|
||||||
|
log.info("[ShieldAttachmentIO] Gespeichert: {}", file);
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.warn("[ShieldAttachmentIO] Speicherfehler: {}", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Map<String, ShieldCategoryAttachment> getAll() {
|
||||||
|
ensureLoaded();
|
||||||
|
return new HashMap<>(CONFIGS);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static synchronized void ensureLoaded() {
|
||||||
|
Path file = resolveFile();
|
||||||
|
if (file != null) {
|
||||||
|
try {
|
||||||
|
long mtime = Files.getLastModifiedTime(file).toMillis();
|
||||||
|
if (!file.equals(cachedFile) || mtime != cachedMtime) {
|
||||||
|
parse(Files.readString(file, StandardCharsets.UTF_8));
|
||||||
|
cachedFile = file;
|
||||||
|
cachedMtime = mtime;
|
||||||
|
}
|
||||||
|
loaded = true;
|
||||||
|
return;
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.warn("[ShieldAttachmentIO] Ladefehler: {}", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!loaded) loadFromClasspath();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sucht {@code shield_attachment.json} im Dateisystem der Dev-Umgebung (Editor/Gradle-Run). */
|
||||||
|
private static Path resolveFile() {
|
||||||
|
for (String base : ASSET_BASES) {
|
||||||
|
Path p = Paths.get(base, CLASSPATH_FILE);
|
||||||
|
if (Files.isRegularFile(p)) return p;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void parse(String json) {
|
||||||
|
CONFIGS.clear();
|
||||||
|
JsonObject obj = JsonParser.parseString(json).getAsJsonObject();
|
||||||
|
for (var entry : obj.entrySet()) {
|
||||||
|
CONFIGS.put(entry.getKey(),
|
||||||
|
GSON.fromJson(entry.getValue(), ShieldCategoryAttachment.class));
|
||||||
|
}
|
||||||
|
log.info("[ShieldAttachmentIO] {} Kategorien geladen", CONFIGS.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package de.blight.common.model;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
/** Attachment-Parameter für einen Schild-Typ (am linken Unterarm). */
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class ShieldCategoryAttachment {
|
||||||
|
private float rotX = -90f;
|
||||||
|
private float rotY = 0f;
|
||||||
|
private float rotZ = 0f;
|
||||||
|
private float elbowOffset = 0.25f;
|
||||||
|
}
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
package de.blight.common.model;
|
||||||
|
|
||||||
|
import com.google.gson.*;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lädt und speichert per-Kategorie Waffen-Attachment-Konfiguration (weapon_attachment.json).
|
||||||
|
* Singleton-Map: wird vom Dateisystem geladen (Dev-Umgebung, siehe ASSET_BASES) und bei jedem
|
||||||
|
* Zugriff gegen die mtime der Datei geprüft, damit Änderungen aus dem Editor ohne Neustart
|
||||||
|
* ankommen. Nur wenn keine Quelldatei auffindbar ist (z.B. gepackter Build), wird einmalig
|
||||||
|
* vom Classpath geladen.
|
||||||
|
*/
|
||||||
|
public final class WeaponAttachmentIO {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(WeaponAttachmentIO.class);
|
||||||
|
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
|
||||||
|
private static final String CLASSPATH_FILE = "weapon_attachment.json";
|
||||||
|
|
||||||
|
/** Siehe AnimationLibrary.ASSET_BASES (blight-game) – hier dupliziert, da blight-common
|
||||||
|
* keine Abhängigkeit auf blight-game haben darf. */
|
||||||
|
private static final String[] ASSET_BASES = {
|
||||||
|
"blight-assets/src/main/resources",
|
||||||
|
"../blight-assets/src/main/resources",
|
||||||
|
"assets",
|
||||||
|
".",
|
||||||
|
};
|
||||||
|
|
||||||
|
private static final Map<String, WeaponCategoryAttachment> CONFIGS = new HashMap<>();
|
||||||
|
private static boolean loaded = false;
|
||||||
|
private static Path cachedFile = null;
|
||||||
|
private static long cachedMtime = -1;
|
||||||
|
|
||||||
|
private WeaponAttachmentIO() {}
|
||||||
|
|
||||||
|
/** Gibt die Attachment-Konfiguration für die gegebene Kategorie zurück (lazy-load vom Classpath). */
|
||||||
|
public static WeaponCategoryAttachment get(ItemSubCategory cat) {
|
||||||
|
ensureLoaded();
|
||||||
|
if (cat == null) return new WeaponCategoryAttachment();
|
||||||
|
return CONFIGS.getOrDefault(cat.name(), new WeaponCategoryAttachment());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void set(ItemSubCategory cat, WeaponCategoryAttachment cfg) {
|
||||||
|
if (cat != null) CONFIGS.put(cat.name(), cfg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Lädt weapon_attachment.json vom Classpath (für Runtime im Spiel). */
|
||||||
|
public static synchronized void loadFromClasspath() {
|
||||||
|
try (InputStream is = WeaponAttachmentIO.class.getClassLoader()
|
||||||
|
.getResourceAsStream(CLASSPATH_FILE)) {
|
||||||
|
if (is == null) {
|
||||||
|
log.warn("[WeaponAttachmentIO] {} nicht auf Classpath gefunden – Defaults verwendet",
|
||||||
|
CLASSPATH_FILE);
|
||||||
|
loaded = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
parse(new String(is.readAllBytes(), StandardCharsets.UTF_8));
|
||||||
|
loaded = true;
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.warn("[WeaponAttachmentIO] Ladefehler: {}", e.getMessage());
|
||||||
|
loaded = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Lädt weapon_attachment.json vom Dateisystem (für Editor). */
|
||||||
|
public static void load(Path file) {
|
||||||
|
if (!Files.exists(file)) return;
|
||||||
|
try {
|
||||||
|
parse(Files.readString(file, StandardCharsets.UTF_8));
|
||||||
|
loaded = true;
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.warn("[WeaponAttachmentIO] Ladefehler: {}", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Speichert die aktuelle Konfiguration in eine JSON-Datei (nur im Editor). */
|
||||||
|
public static void save(Path file) {
|
||||||
|
try {
|
||||||
|
Files.writeString(file, GSON.toJson(CONFIGS), StandardCharsets.UTF_8);
|
||||||
|
log.info("[WeaponAttachmentIO] Gespeichert: {}", file);
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.warn("[WeaponAttachmentIO] Speicherfehler: {}", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Gibt eine Kopie der aktuellen Config-Map zurück (für Editor-Anzeige). */
|
||||||
|
public static Map<String, WeaponCategoryAttachment> getAll() {
|
||||||
|
ensureLoaded();
|
||||||
|
return new HashMap<>(CONFIGS);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static synchronized void ensureLoaded() {
|
||||||
|
Path file = resolveFile();
|
||||||
|
if (file != null) {
|
||||||
|
try {
|
||||||
|
long mtime = Files.getLastModifiedTime(file).toMillis();
|
||||||
|
if (!file.equals(cachedFile) || mtime != cachedMtime) {
|
||||||
|
parse(Files.readString(file, StandardCharsets.UTF_8));
|
||||||
|
cachedFile = file;
|
||||||
|
cachedMtime = mtime;
|
||||||
|
}
|
||||||
|
loaded = true;
|
||||||
|
return;
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.warn("[WeaponAttachmentIO] Ladefehler: {}", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!loaded) loadFromClasspath();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sucht {@code weapon_attachment.json} im Dateisystem der Dev-Umgebung (Editor/Gradle-Run). */
|
||||||
|
private static Path resolveFile() {
|
||||||
|
for (String base : ASSET_BASES) {
|
||||||
|
Path p = Paths.get(base, CLASSPATH_FILE);
|
||||||
|
if (Files.isRegularFile(p)) return p;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void parse(String json) {
|
||||||
|
CONFIGS.clear();
|
||||||
|
JsonObject obj = JsonParser.parseString(json).getAsJsonObject();
|
||||||
|
for (var entry : obj.entrySet()) {
|
||||||
|
CONFIGS.put(entry.getKey(),
|
||||||
|
GSON.fromJson(entry.getValue(), WeaponCategoryAttachment.class));
|
||||||
|
}
|
||||||
|
log.info("[WeaponAttachmentIO] {} Kategorien geladen", CONFIGS.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package de.blight.common.model;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
/** Attachment-Parameter für eine Waffen-Kategorie (ersetzt den hardcodierten HAND_GRIP_OFFSET). */
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class WeaponCategoryAttachment {
|
||||||
|
/** Rechte Hand: Offset in Waffen-Local-Space. Standardwert = bisheriger HAND_GRIP_OFFSET. */
|
||||||
|
private float handOffX = ItemGrip.HAND_GRIP_OFFSET_X;
|
||||||
|
private float handOffY = ItemGrip.HAND_GRIP_OFFSET_Y;
|
||||||
|
private float handOffZ = ItemGrip.HAND_GRIP_OFFSET_Z;
|
||||||
|
/** Linke Hand (Zweihänder): Korrektur auf g2 in Waffen-Local-Space. */
|
||||||
|
private float hand2OffX = 0f;
|
||||||
|
private float hand2OffY = 0f;
|
||||||
|
private float hand2OffZ = 0f;
|
||||||
|
}
|
||||||
@@ -174,7 +174,7 @@ public class EditorApp extends Application {
|
|||||||
animPreviewController = new de.blight.editor.toolset.anim.AnimPreviewController(toolCtx);
|
animPreviewController = new de.blight.editor.toolset.anim.AnimPreviewController(toolCtx);
|
||||||
animSetController = new de.blight.editor.toolset.anim.AnimSetController(toolCtx);
|
animSetController = new de.blight.editor.toolset.anim.AnimSetController(toolCtx);
|
||||||
objectController = new de.blight.editor.toolset.object.ObjectController(toolCtx);
|
objectController = new de.blight.editor.toolset.object.ObjectController(toolCtx);
|
||||||
modelEditorController = new de.blight.editor.toolset.model.ModelEditorController(toolCtx, () -> assetController.openOverlay());
|
modelEditorController = new de.blight.editor.toolset.model.ModelEditorController(toolCtx, () -> { assetController.openOverlay(); assetController.expandModelsNode(); });
|
||||||
modelEditorController.setObjectController(objectController);
|
modelEditorController.setObjectController(objectController);
|
||||||
atmosphereController = new de.blight.editor.toolset.atmosphere.AtmosphereController(toolCtx);
|
atmosphereController = new de.blight.editor.toolset.atmosphere.AtmosphereController(toolCtx);
|
||||||
waterController = new de.blight.editor.toolset.water.WaterController(toolCtx);
|
waterController = new de.blight.editor.toolset.water.WaterController(toolCtx);
|
||||||
|
|||||||
@@ -1237,6 +1237,39 @@ public class SharedInput {
|
|||||||
/** Klingenrichtung im Modellraum: -1 = Klinge bei -Z, +1 = Klinge bei +Z. Wird von guessGripPosition gesetzt. */
|
/** Klingenrichtung im Modellraum: -1 = Klinge bei -Z, +1 = Klinge bei +Z. Wird von guessGripPosition gesetzt. */
|
||||||
public volatile float gripEditorBladeDirZ = -1f;
|
public volatile float gripEditorBladeDirZ = -1f;
|
||||||
|
|
||||||
|
// ── Charakter-Preview im Grip-Editor ─────────────────────────────────────
|
||||||
|
/** JFX→JME: Charakter-Preview ein- (true) oder ausschalten (false). */
|
||||||
|
public volatile boolean gripEditorCharPreview = false;
|
||||||
|
/** JFX→JME: ItemSubCategory-Name der aktuell editierten Waffe. */
|
||||||
|
public volatile String gripEditorSubCategory = null;
|
||||||
|
/** JME→JFX: Charakter-Preview ist bereit / aktiv. */
|
||||||
|
public volatile boolean gripEditorCharPreviewReady = false;
|
||||||
|
/** JFX→JME: Kategorie-Offsets (rechte Hand) für Live-Update im Preview. */
|
||||||
|
public volatile float gripEditorCatHandOffX = 0.1f;
|
||||||
|
public volatile float gripEditorCatHandOffY = 0f;
|
||||||
|
public volatile float gripEditorCatHandOffZ = 0.05f;
|
||||||
|
/** JFX→JME: Kategorie-Offsets (linke Hand, nur Zweihänder). */
|
||||||
|
public volatile float gripEditorCatHand2OffX = 0f;
|
||||||
|
public volatile float gripEditorCatHand2OffY = 0f;
|
||||||
|
public volatile float gripEditorCatHand2OffZ = 0f;
|
||||||
|
/** JFX→JME: true wenn Kategorie-Offsets geändert wurden. */
|
||||||
|
public volatile boolean gripEditorCatOffsetsDirty = false;
|
||||||
|
|
||||||
|
// ── Attachment-Editor (eigenständige Ansicht für Hand-Anker pro Kategorie) ─
|
||||||
|
public volatile boolean attachmentEditorActive = false;
|
||||||
|
|
||||||
|
// ── Schild-Attachment-Editor ──────────────────────────────────────────────
|
||||||
|
public volatile boolean shieldEditorActive = false;
|
||||||
|
public volatile String shieldEditorSubCategory = null;
|
||||||
|
public volatile float shieldEditorRotX = -90f;
|
||||||
|
public volatile float shieldEditorRotY = 0f;
|
||||||
|
public volatile float shieldEditorRotZ = 0f;
|
||||||
|
public volatile float shieldEditorElbowOffset = 0.25f;
|
||||||
|
public volatile float shieldEditorGripX = 0f;
|
||||||
|
public volatile float shieldEditorGripY = 0f;
|
||||||
|
public volatile float shieldEditorGripZ = 0f;
|
||||||
|
public volatile boolean shieldEditorOffsetsDirty = false;
|
||||||
|
|
||||||
/** Klick/Drag im Viewport im Sculpt-Modus. action 0=links, 1=rechts. */
|
/** Klick/Drag im Viewport im Sculpt-Modus. action 0=links, 1=rechts. */
|
||||||
public record SculptEdit(float screenX, float screenY, int action) {}
|
public record SculptEdit(float screenX, float screenY, int action) {}
|
||||||
public final ConcurrentLinkedQueue<SculptEdit> sculptEditQueue = new ConcurrentLinkedQueue<>();
|
public final ConcurrentLinkedQueue<SculptEdit> sculptEditQueue = new ConcurrentLinkedQueue<>();
|
||||||
|
|||||||
@@ -34,6 +34,12 @@ import com.jme3.effect.ParticleMesh;
|
|||||||
import com.jme3.material.RenderState;
|
import com.jme3.material.RenderState;
|
||||||
import com.jme3.texture.Texture;
|
import com.jme3.texture.Texture;
|
||||||
import com.jme3.util.BufferUtils;
|
import com.jme3.util.BufferUtils;
|
||||||
|
import com.jme3.anim.AnimComposer;
|
||||||
|
import com.jme3.math.Matrix4f;
|
||||||
|
import de.blight.common.model.ItemGrip;
|
||||||
|
import de.blight.common.model.ItemSubCategory;
|
||||||
|
import de.blight.common.model.WeaponAttachmentIO;
|
||||||
|
import de.blight.common.model.WeaponCategoryAttachment;
|
||||||
import de.blight.editor.SharedInput;
|
import de.blight.editor.SharedInput;
|
||||||
import de.blight.editor.util.ImpostorUtil;
|
import de.blight.editor.util.ImpostorUtil;
|
||||||
import de.blight.editor.util.ModelExportUtil;
|
import de.blight.editor.util.ModelExportUtil;
|
||||||
@@ -107,6 +113,17 @@ public class ModelEditorState extends BaseAppState {
|
|||||||
private float gripPosX = 0f, gripPosY = 0f, gripPosZ = 0f;
|
private float gripPosX = 0f, gripPosY = 0f, gripPosZ = 0f;
|
||||||
private float gripRotX = 0f, gripRotY = 0f, gripRotZ = 0f;
|
private float gripRotX = 0f, gripRotY = 0f, gripRotZ = 0f;
|
||||||
private float grip2PosX = 0f, grip2PosY = 0f, grip2PosZ = 0f;
|
private float grip2PosX = 0f, grip2PosY = 0f, grip2PosZ = 0f;
|
||||||
|
|
||||||
|
// ── Charakter-Preview ────────────────────────────────────────────────────
|
||||||
|
private Node charPreviewNode = null;
|
||||||
|
private Spatial charWeaponSpatial = null;
|
||||||
|
private Node charHandNode = null; // RightHand-Attachment
|
||||||
|
private Node charLeftHandNode = null; // LeftHand-Attachment
|
||||||
|
private Node charPalmNode = null; // RightHandMiddle1-Attachment
|
||||||
|
private Quaternion charHandBindRot = null;
|
||||||
|
private Quaternion charLeftHandBindRot = null;
|
||||||
|
private Vector3f charPalmOffset = null;
|
||||||
|
private int charPalmDelayFrames = 0;
|
||||||
private float interactableOffsetZ = 0f;
|
private float interactableOffsetZ = 0f;
|
||||||
private float interactableRotY = 0f;
|
private float interactableRotY = 0f;
|
||||||
|
|
||||||
@@ -244,12 +261,7 @@ public class ModelEditorState extends BaseAppState {
|
|||||||
guessGripPosition();
|
guessGripPosition();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (input.modelEditorEmbedLodsRequest) {
|
// Modell laden – MUSS vor enableCharPreview stehen, damit currentPath aktuell ist
|
||||||
input.modelEditorEmbedLodsRequest = false;
|
|
||||||
embedLodsAndSave(input.modelEditorEmbedLodsPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Modell laden
|
|
||||||
String openPath = input.modelEditorOpenPath;
|
String openPath = input.modelEditorOpenPath;
|
||||||
if (openPath != null) {
|
if (openPath != null) {
|
||||||
input.modelEditorOpenPath = null;
|
input.modelEditorOpenPath = null;
|
||||||
@@ -260,8 +272,50 @@ public class ModelEditorState extends BaseAppState {
|
|||||||
loadModel(openPath);
|
loadModel(openPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Grip-Gizmo aktualisieren
|
// Charakter-Preview ein-/ausschalten
|
||||||
if (input.gripEditorActive && input.gripEditorGizmoDirty) {
|
boolean wantsPreview = (input.gripEditorCharPreview && input.gripEditorActive)
|
||||||
|
|| input.attachmentEditorActive
|
||||||
|
|| input.shieldEditorActive;
|
||||||
|
if (wantsPreview && charPreviewNode == null) {
|
||||||
|
enableCharPreview();
|
||||||
|
} else if (!wantsPreview && charPreviewNode != null) {
|
||||||
|
disableCharPreview();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Palm-Offset nach 2 Frames berechnen
|
||||||
|
if (charPalmDelayFrames > 0 && charPalmNode != null && charHandNode != null) {
|
||||||
|
if (--charPalmDelayFrames == 0) {
|
||||||
|
Vector3f delta = charPalmNode.getWorldTranslation()
|
||||||
|
.subtract(charHandNode.getWorldTranslation());
|
||||||
|
charPalmOffset = charHandNode.getWorldRotation().inverse().mult(delta);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Waffe/Schild im Charakter-Preview jedes Frame neu positionieren
|
||||||
|
if (charPreviewNode != null && charWeaponSpatial != null && charHandNode != null) {
|
||||||
|
updateCharPreviewWeapon();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Orbit-Kamera im Charakter-Preview immer um den aktuellen Hand-Ankerpunkt drehen
|
||||||
|
// (statt um die Bounding-Box der isolierten Waffe, die am Boden/"Fuß" verankert ist)
|
||||||
|
if (charPreviewNode != null) {
|
||||||
|
Vector3f anchor = computeCharAnchorWorld();
|
||||||
|
if (anchor != null) orbitCenter = anchor;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input.modelEditorEmbedLodsRequest) {
|
||||||
|
input.modelEditorEmbedLodsRequest = false;
|
||||||
|
embedLodsAndSave(input.modelEditorEmbedLodsPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invariante: wenn Charakter-Preview aktiv, standalone-Modell immer ausblenden
|
||||||
|
if (charPreviewNode != null) {
|
||||||
|
if (modelWrapper != null) modelWrapper.setCullHint(Spatial.CullHint.Always);
|
||||||
|
if (gripGizmoNode != null) gripGizmoNode.setCullHint(Spatial.CullHint.Always);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Grip-Gizmo aktualisieren (nicht im reinen Attachment-Editor-Modus)
|
||||||
|
if (input.gripEditorActive && !input.attachmentEditorActive && input.gripEditorGizmoDirty) {
|
||||||
input.gripEditorGizmoDirty = false;
|
input.gripEditorGizmoDirty = false;
|
||||||
gripPosX = input.gripEditorPosX;
|
gripPosX = input.gripEditorPosX;
|
||||||
gripPosY = input.gripEditorPosY;
|
gripPosY = input.gripEditorPosY;
|
||||||
@@ -799,6 +853,7 @@ public class ModelEditorState extends BaseAppState {
|
|||||||
originalSpatial = null;
|
originalSpatial = null;
|
||||||
interactableArrowNode = null;
|
interactableArrowNode = null;
|
||||||
gripGizmoNode = null;
|
gripGizmoNode = null;
|
||||||
|
disableCharPreview();
|
||||||
input.gripEditorActive = false;
|
input.gripEditorActive = false;
|
||||||
camLight = null;
|
camLight = null;
|
||||||
mainLight = null;
|
mainLight = null;
|
||||||
@@ -1503,7 +1558,7 @@ public class ModelEditorState extends BaseAppState {
|
|||||||
|
|
||||||
// Schritt 3: Richtung A→B auf +Z ausrichten
|
// Schritt 3: Richtung A→B auf +Z ausrichten
|
||||||
Vector3f bladeDir = B.subtract(A).normalizeLocal();
|
Vector3f bladeDir = B.subtract(A).normalizeLocal();
|
||||||
if (bladeDir.z < 0) bladeDir.negateLocal(); // +Z-Richtung bevorzugen
|
if (bladeDir.z > 0) bladeDir.negateLocal(); // -Z-Richtung bevorzugen
|
||||||
Quaternion rot1 = awRotateToZ(bladeDir);
|
Quaternion rot1 = awRotateToZ(bladeDir);
|
||||||
|
|
||||||
// Mitte der Hauptachse → Ursprung
|
// Mitte der Hauptachse → Ursprung
|
||||||
@@ -1561,11 +1616,11 @@ public class ModelEditorState extends BaseAppState {
|
|||||||
float guardNorm = (float) guardBucket / (BKTS - 1);
|
float guardNorm = (float) guardBucket / (BKTS - 1);
|
||||||
log.info("[ModelEditor] AlignWeapon: Parierstange bei normZ={} (Bucket {}/{})",
|
log.info("[ModelEditor] AlignWeapon: Parierstange bei normZ={} (Bucket {}/{})",
|
||||||
String.format("%.2f", guardNorm), guardBucket, BKTS);
|
String.format("%.2f", guardNorm), guardBucket, BKTS);
|
||||||
if (guardNorm > 0.5f) {
|
if (guardNorm < 0.5f) {
|
||||||
// Parierstange in +Z-Hälfte → Pommel bei +Z, Klinge bei -Z → 180° um X flippen
|
// Parierstange in -Z-Hälfte → Pommel bei -Z, Klinge bei +Z → 180° um X flippen
|
||||||
Quaternion flip = new Quaternion().fromAngles(FastMath.PI, 0f, 0f);
|
Quaternion flip = new Quaternion().fromAngles(FastMath.PI, 0f, 0f);
|
||||||
combined = flip.mult(combined);
|
combined = flip.mult(combined);
|
||||||
log.info("[ModelEditor] AlignWeapon: Pommel bei +Z detektiert → Schwert geflippt");
|
log.info("[ModelEditor] AlignWeapon: Pommel bei -Z detektiert → Schwert geflippt");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2024,4 +2079,305 @@ public class ModelEditorState extends BaseAppState {
|
|||||||
float dz = -cosEle * (float) Math.cos(azi);
|
float dz = -cosEle * (float) Math.cos(azi);
|
||||||
return new Vector3f(dx, dy, dz).normalizeLocal();
|
return new Vector3f(dx, dy, dz).normalizeLocal();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Charakter-Preview ────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
private void enableCharPreview() {
|
||||||
|
if (previewRoot == null || currentPath == null) return;
|
||||||
|
try {
|
||||||
|
Spatial hero = app.getAssetManager().loadModel("Characters/Models/hero.j3o");
|
||||||
|
charPreviewNode = new Node("charPreview");
|
||||||
|
charPreviewNode.attachChild(hero);
|
||||||
|
previewRoot.attachChild(charPreviewNode);
|
||||||
|
|
||||||
|
SkinningControl skin = findSkinCtrl(hero);
|
||||||
|
if (skin != null) {
|
||||||
|
charHandBindRot = extractJointBindRot(skin, "mixamorig:RightHand");
|
||||||
|
charLeftHandBindRot = extractJointBindRot(skin, "mixamorig:LeftHand");
|
||||||
|
try { charHandNode = skin.getAttachmentsNode("mixamorig:RightHand"); } catch (Exception e) { log.warn("[CharPreview] RightHand: {}", e.getMessage()); }
|
||||||
|
try { charLeftHandNode = skin.getAttachmentsNode("mixamorig:LeftHand"); } catch (Exception e) { log.warn("[CharPreview] LeftHand: {}", e.getMessage()); }
|
||||||
|
try { charPalmNode = skin.getAttachmentsNode("mixamorig:RightHandMiddle1"); charPalmDelayFrames = 2; } catch (Exception e) { log.warn("[CharPreview] Palm: {}", e.getMessage()); }
|
||||||
|
|
||||||
|
AnimComposer anim = findAnimComposer(hero);
|
||||||
|
if (anim != null) {
|
||||||
|
String subCat = input.shieldEditorActive
|
||||||
|
? input.shieldEditorSubCategory : input.gripEditorSubCategory;
|
||||||
|
String clipName = previewAnimName(subCat);
|
||||||
|
loadClipToComposer(anim, skin, clipName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
charWeaponSpatial = app.getAssetManager().loadModel(currentPath);
|
||||||
|
charPreviewNode.attachChild(charWeaponSpatial);
|
||||||
|
|
||||||
|
// Standalone-Waffe ausblenden
|
||||||
|
if (modelWrapper != null) modelWrapper.setCullHint(Spatial.CullHint.Always);
|
||||||
|
if (gripGizmoNode != null) gripGizmoNode.setCullHint(Spatial.CullHint.Always);
|
||||||
|
|
||||||
|
orbitDist = 2.5f;
|
||||||
|
orbitYaw = 135f;
|
||||||
|
orbitPitch = 15f;
|
||||||
|
input.gripEditorCharPreviewReady = true;
|
||||||
|
log.info("[CharPreview] Aktiviert");
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("[CharPreview] Fehler beim Laden: {}", e.getMessage());
|
||||||
|
disableCharPreview();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void disableCharPreview() {
|
||||||
|
if (charPreviewNode != null) {
|
||||||
|
charPreviewNode.removeFromParent();
|
||||||
|
charPreviewNode = null;
|
||||||
|
charWeaponSpatial = null;
|
||||||
|
charHandNode = null;
|
||||||
|
charLeftHandNode = null;
|
||||||
|
charPalmNode = null;
|
||||||
|
charHandBindRot = null;
|
||||||
|
charLeftHandBindRot = null;
|
||||||
|
charPalmOffset = null;
|
||||||
|
}
|
||||||
|
if (modelWrapper != null) modelWrapper.setCullHint(Spatial.CullHint.Inherit);
|
||||||
|
if (gripGizmoNode != null) gripGizmoNode.setCullHint(Spatial.CullHint.Always);
|
||||||
|
input.gripEditorCharPreviewReady = false;
|
||||||
|
updateBounds(); // Orbit-Zentrum zurück auf die Bounding-Box der isolierten Waffe
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Liefert die aktuelle Position des Hand-Ankerpunkts (Waffen-Griff bzw. Schild-Anker)
|
||||||
|
* in Weltkoordinaten, oder null wenn kein Charakter-Preview aktiv ist.
|
||||||
|
*/
|
||||||
|
private Vector3f computeCharAnchorWorld() {
|
||||||
|
if (charPreviewNode == null) return null;
|
||||||
|
Quaternion charWorldRot = charPreviewNode.getWorldRotation();
|
||||||
|
Quaternion charWorldRotInv = charWorldRot.inverse();
|
||||||
|
Vector3f charWorldPos = charPreviewNode.getWorldTranslation();
|
||||||
|
|
||||||
|
if (input.shieldEditorActive) {
|
||||||
|
if (charLeftHandNode == null) return null;
|
||||||
|
Vector3f leftBoneInChar = charWorldRotInv.mult(
|
||||||
|
charLeftHandNode.getWorldTranslation().subtract(charWorldPos));
|
||||||
|
Quaternion leftBoneRotInChar = charWorldRotInv.mult(charLeftHandNode.getWorldRotation());
|
||||||
|
Vector3f anchorInChar = leftBoneInChar.add(
|
||||||
|
leftBoneRotInChar.mult(new Vector3f(0f, -input.shieldEditorElbowOffset, 0f)));
|
||||||
|
return charWorldPos.add(charWorldRot.mult(anchorInChar));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (charHandNode == null) return null;
|
||||||
|
Vector3f boneInChar = charWorldRotInv.mult(
|
||||||
|
charHandNode.getWorldTranslation().subtract(charWorldPos));
|
||||||
|
Quaternion boneRotInChar = charWorldRotInv.mult(charHandNode.getWorldRotation());
|
||||||
|
Vector3f palmOff = charPalmOffset;
|
||||||
|
Vector3f attachInChar = (palmOff != null)
|
||||||
|
? boneInChar.add(boneRotInChar.mult(palmOff.mult(0.5f)))
|
||||||
|
: boneInChar;
|
||||||
|
return charWorldPos.add(charWorldRot.mult(attachInChar));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateCharPreviewWeapon() {
|
||||||
|
if (input.shieldEditorActive) {
|
||||||
|
updateCharPreviewShield();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Kategorie-Offsets aus SharedInput lesen (live-update)
|
||||||
|
WeaponCategoryAttachment cfg = new WeaponCategoryAttachment();
|
||||||
|
cfg.setHandOffX(input.gripEditorCatHandOffX);
|
||||||
|
cfg.setHandOffY(input.gripEditorCatHandOffY);
|
||||||
|
cfg.setHandOffZ(input.gripEditorCatHandOffZ);
|
||||||
|
cfg.setHand2OffX(input.gripEditorCatHand2OffX);
|
||||||
|
cfg.setHand2OffY(input.gripEditorCatHand2OffY);
|
||||||
|
cfg.setHand2OffZ(input.gripEditorCatHand2OffZ);
|
||||||
|
|
||||||
|
// Grip-Werte aus SharedInput (Spinner = display-Werte, JSON-Werte = + HAND_GRIP_OFFSET)
|
||||||
|
float g1x = input.gripEditorPosX + ItemGrip.HAND_GRIP_OFFSET_X;
|
||||||
|
float g1y = input.gripEditorPosY + ItemGrip.HAND_GRIP_OFFSET_Y;
|
||||||
|
float g1z = input.gripEditorPosZ + ItemGrip.HAND_GRIP_OFFSET_Z;
|
||||||
|
float g2x = input.gripEditorGrip2PosX;
|
||||||
|
float g2y = input.gripEditorGrip2PosY;
|
||||||
|
float g2z = input.gripEditorGrip2PosZ;
|
||||||
|
boolean hasGrip2 = g2x != 0f || g2y != 0f || g2z != 0f;
|
||||||
|
boolean bladeAtMinusZ = hasGrip2 ? (g2z > g1z) : (g1z > 0f);
|
||||||
|
boolean isTwoHand = hasGrip2 && bladeAtMinusZ;
|
||||||
|
|
||||||
|
Quaternion charWorldRot = charPreviewNode.getWorldRotation();
|
||||||
|
Quaternion charWorldRotInv = charWorldRot.inverse();
|
||||||
|
Vector3f charWorldPos = charPreviewNode.getWorldTranslation();
|
||||||
|
|
||||||
|
Vector3f boneInChar = charWorldRotInv.mult(charHandNode.getWorldTranslation().subtract(charWorldPos));
|
||||||
|
Quaternion boneRotInChar = charWorldRotInv.mult(charHandNode.getWorldRotation());
|
||||||
|
Vector3f palmOff = charPalmOffset;
|
||||||
|
Vector3f attachInChar = (palmOff != null)
|
||||||
|
? boneInChar.add(boneRotInChar.mult(palmOff.mult(0.5f)))
|
||||||
|
: boneInChar;
|
||||||
|
|
||||||
|
float DEG_TO_RAD = (float) Math.PI / 180f;
|
||||||
|
Quaternion gripRot = new Quaternion().fromAngles(
|
||||||
|
DEG_TO_RAD * input.gripEditorRotX,
|
||||||
|
DEG_TO_RAD * input.gripEditorRotY,
|
||||||
|
DEG_TO_RAD * input.gripEditorRotZ);
|
||||||
|
Quaternion animDelta = (charHandBindRot != null)
|
||||||
|
? boneRotInChar.mult(charHandBindRot.inverse())
|
||||||
|
: boneRotInChar;
|
||||||
|
Quaternion bladeFlip = bladeAtMinusZ
|
||||||
|
? new Quaternion().fromAngles(com.jme3.math.FastMath.PI, 0f, 0f)
|
||||||
|
: new Quaternion();
|
||||||
|
Quaternion weaponRot = animDelta.mult(bladeFlip).mult(gripRot.inverse());
|
||||||
|
|
||||||
|
Vector3f handOff = bladeAtMinusZ
|
||||||
|
? new Vector3f(cfg.getHandOffX(), cfg.getHandOffY(), -cfg.getHandOffZ())
|
||||||
|
: new Vector3f(cfg.getHandOffX(), cfg.getHandOffY(), cfg.getHandOffZ());
|
||||||
|
|
||||||
|
Quaternion finalRot;
|
||||||
|
Vector3f finalPos;
|
||||||
|
|
||||||
|
if (isTwoHand && charLeftHandNode != null) {
|
||||||
|
Vector3f hand2Off = bladeAtMinusZ
|
||||||
|
? new Vector3f(cfg.getHand2OffX(), cfg.getHand2OffY(), -cfg.getHand2OffZ())
|
||||||
|
: new Vector3f(cfg.getHand2OffX(), cfg.getHand2OffY(), cfg.getHand2OffZ());
|
||||||
|
Vector3f g1 = new Vector3f(g1x, g1y, g1z);
|
||||||
|
Vector3f g2 = new Vector3f(g2x, g2y, g2z);
|
||||||
|
Vector3f g1eff = g1.subtract(handOff);
|
||||||
|
Vector3f g2eff = g2.add(hand2Off);
|
||||||
|
|
||||||
|
Vector3f leftHandInChar = charWorldRotInv.mult(
|
||||||
|
charLeftHandNode.getWorldTranslation().subtract(charWorldPos));
|
||||||
|
Vector3f currentGripDir = weaponRot.mult(g2eff.subtract(g1eff).normalizeLocal());
|
||||||
|
Vector3f targetDir = leftHandInChar.subtract(attachInChar).normalizeLocal();
|
||||||
|
finalRot = swingFromTo(currentGripDir, targetDir).mult(weaponRot);
|
||||||
|
finalPos = attachInChar.subtract(finalRot.mult(g1eff));
|
||||||
|
} else {
|
||||||
|
Vector3f gripPos = new Vector3f(g1x, g1y, g1z);
|
||||||
|
finalRot = weaponRot;
|
||||||
|
finalPos = attachInChar.subtract(finalRot.mult(gripPos)).addLocal(finalRot.mult(handOff));
|
||||||
|
}
|
||||||
|
|
||||||
|
charWeaponSpatial.setLocalRotation(finalRot);
|
||||||
|
charWeaponSpatial.setLocalTranslation(finalPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateCharPreviewShield() {
|
||||||
|
if (charLeftHandNode == null) return;
|
||||||
|
float DEG_TO_RAD = (float) Math.PI / 180f;
|
||||||
|
Quaternion charWorldRotInv = charPreviewNode.getWorldRotation().inverse();
|
||||||
|
Vector3f charWorldPos = charPreviewNode.getWorldTranslation();
|
||||||
|
|
||||||
|
Vector3f leftBoneInChar = charWorldRotInv.mult(
|
||||||
|
charLeftHandNode.getWorldTranslation().subtract(charWorldPos));
|
||||||
|
Quaternion leftBoneRotInChar = charWorldRotInv.mult(charLeftHandNode.getWorldRotation());
|
||||||
|
|
||||||
|
Quaternion animDelta = (charLeftHandBindRot != null)
|
||||||
|
? leftBoneRotInChar.mult(charLeftHandBindRot.inverse())
|
||||||
|
: leftBoneRotInChar;
|
||||||
|
Quaternion armRot = new Quaternion().fromAngles(
|
||||||
|
DEG_TO_RAD * input.shieldEditorRotX,
|
||||||
|
DEG_TO_RAD * input.shieldEditorRotY,
|
||||||
|
DEG_TO_RAD * input.shieldEditorRotZ);
|
||||||
|
Quaternion shieldRot = animDelta.mult(armRot);
|
||||||
|
|
||||||
|
Vector3f anchor = leftBoneInChar.add(
|
||||||
|
leftBoneRotInChar.mult(new Vector3f(0f, -input.shieldEditorElbowOffset, 0f)));
|
||||||
|
Vector3f gripPos = new Vector3f(
|
||||||
|
input.shieldEditorGripX, input.shieldEditorGripY, input.shieldEditorGripZ);
|
||||||
|
|
||||||
|
charWeaponSpatial.setLocalRotation(shieldRot);
|
||||||
|
charWeaponSpatial.setLocalTranslation(anchor.subtract(shieldRot.mult(gripPos)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String previewAnimName(String subCategory) {
|
||||||
|
if (subCategory == null) return "Idle";
|
||||||
|
return switch (subCategory) {
|
||||||
|
case "TWO_HANDED_SWORD" -> "Great Sword Idle";
|
||||||
|
case "SWORD", "SHIELD" -> "Sword And Shield Idle";
|
||||||
|
default -> "Idle";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadClipToComposer(AnimComposer ac, SkinningControl dstSC, String clipName) {
|
||||||
|
if (ac.getAnimClipsNames().contains(clipName)) {
|
||||||
|
ac.setCurrentAction(clipName);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
String assetKey = "Characters/clips/" + clipName + ".j3o";
|
||||||
|
Spatial clipModel = app.getAssetManager().loadModel(assetKey);
|
||||||
|
AnimComposer clipAC = findAnimComposer(clipModel);
|
||||||
|
SkinningControl clipSC = findSkinCtrl(clipModel);
|
||||||
|
if (clipAC == null || clipAC.getAnimClipsNames().isEmpty()) {
|
||||||
|
log.warn("[CharPreview] Clip-Datei leer oder kein AnimComposer: {}", assetKey);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
com.jme3.anim.Armature srcArm = clipSC != null ? clipSC.getArmature() : null;
|
||||||
|
com.jme3.anim.Armature dstArm = dstSC != null ? dstSC.getArmature() : null;
|
||||||
|
String firstName = clipAC.getAnimClipsNames().iterator().next();
|
||||||
|
com.jme3.anim.AnimClip src = clipAC.getAnimClip(firstName);
|
||||||
|
com.jme3.anim.AnimClip target;
|
||||||
|
if (srcArm != null && dstArm != null) {
|
||||||
|
target = de.blight.game.animation.RetargetingSystem.retarget(src, srcArm, dstArm);
|
||||||
|
if (target == null) target = src;
|
||||||
|
} else {
|
||||||
|
target = src;
|
||||||
|
}
|
||||||
|
if (!clipName.equals(target.getName())) {
|
||||||
|
com.jme3.anim.AnimClip renamed = new com.jme3.anim.AnimClip(clipName);
|
||||||
|
renamed.setTracks(target.getTracks());
|
||||||
|
target = renamed;
|
||||||
|
}
|
||||||
|
ac.addAnimClip(target);
|
||||||
|
ac.setCurrentAction(clipName);
|
||||||
|
log.info("[CharPreview] Clip '{}' geladen und gestartet", clipName);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("[CharPreview] Clip '{}' konnte nicht geladen werden: {}", clipName, e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SkinningControl findSkinCtrl(Spatial s) {
|
||||||
|
SkinningControl sc = s.getControl(SkinningControl.class);
|
||||||
|
if (sc != null) return sc;
|
||||||
|
if (s instanceof Node n) {
|
||||||
|
for (Spatial c : n.getChildren()) {
|
||||||
|
sc = findSkinCtrl(c);
|
||||||
|
if (sc != null) return sc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static AnimComposer findAnimComposer(Spatial s) {
|
||||||
|
AnimComposer ac = s.getControl(AnimComposer.class);
|
||||||
|
if (ac != null) return ac;
|
||||||
|
if (s instanceof Node n) {
|
||||||
|
for (Spatial c : n.getChildren()) {
|
||||||
|
ac = findAnimComposer(c);
|
||||||
|
if (ac != null) return ac;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Quaternion extractJointBindRot(SkinningControl sc, String jointName) {
|
||||||
|
com.jme3.anim.Armature arm = sc.getArmature();
|
||||||
|
if (arm == null) return null;
|
||||||
|
com.jme3.anim.Joint joint = arm.getJoint(jointName);
|
||||||
|
if (joint == null) return null;
|
||||||
|
Matrix4f invBind = joint.getInverseModelBindMatrix();
|
||||||
|
if (invBind == null) return null;
|
||||||
|
Matrix4f bindMat = invBind.invert();
|
||||||
|
if (bindMat == null) return null;
|
||||||
|
return bindMat.toRotationQuat().normalizeLocal();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Quaternion swingFromTo(Vector3f from, Vector3f to) {
|
||||||
|
float dot = com.jme3.math.FastMath.clamp(from.dot(to), -1f, 1f);
|
||||||
|
Vector3f axis = from.cross(to);
|
||||||
|
float len = axis.length();
|
||||||
|
if (len < 0.001f) {
|
||||||
|
if (dot > 0f) return new Quaternion();
|
||||||
|
Vector3f perp = Math.abs(from.x) <= Math.abs(from.y)
|
||||||
|
? from.cross(Vector3f.UNIT_X).normalizeLocal()
|
||||||
|
: from.cross(Vector3f.UNIT_Y).normalizeLocal();
|
||||||
|
return new Quaternion().fromAngleNormalAxis(com.jme3.math.FastMath.PI, perp);
|
||||||
|
}
|
||||||
|
return new Quaternion().fromAngleNormalAxis(com.jme3.math.FastMath.acos(dot),
|
||||||
|
axis.divideLocal(len));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,279 @@
|
|||||||
|
package de.blight.editor.toolset.content;
|
||||||
|
|
||||||
|
import de.blight.common.model.ItemGrip;
|
||||||
|
import de.blight.common.model.ItemSubCategory;
|
||||||
|
import de.blight.common.model.WeaponAttachmentIO;
|
||||||
|
import de.blight.common.model.WeaponCategoryAttachment;
|
||||||
|
import de.blight.editor.SharedInput;
|
||||||
|
import de.blight.editor.ToolContext;
|
||||||
|
import javafx.geometry.Insets;
|
||||||
|
import javafx.geometry.Orientation;
|
||||||
|
import javafx.geometry.Pos;
|
||||||
|
import javafx.scene.control.*;
|
||||||
|
import javafx.scene.layout.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Eigenständige Ansicht zum Bearbeiten der per-Kategorie Hand-Anker-Offsets.
|
||||||
|
* Zeigt den Helden direkt mit Waffe in der Idle-Animation – keine Griff-Gizmos.
|
||||||
|
*/
|
||||||
|
public class AttachmentEditorController {
|
||||||
|
|
||||||
|
private final ToolContext ctx;
|
||||||
|
|
||||||
|
private Spinner<Double> handOffXSpin, handOffYSpin, handOffZSpin;
|
||||||
|
private Spinner<Double> hand2OffXSpin, hand2OffYSpin, hand2OffZSpin;
|
||||||
|
|
||||||
|
private boolean suppressEvents = false;
|
||||||
|
private ItemSubCategory itemSubCategory = null;
|
||||||
|
private Runnable onReturn = null;
|
||||||
|
|
||||||
|
public AttachmentEditorController(ToolContext ctx) {
|
||||||
|
this.ctx = ctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnReturn(Runnable r) { this.onReturn = r; }
|
||||||
|
|
||||||
|
// ── Öffnen ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
public void open(String modelPath, ItemSubCategory subCategory, ItemGrip grip) {
|
||||||
|
itemSubCategory = subCategory;
|
||||||
|
|
||||||
|
SharedInput input = ctx.input();
|
||||||
|
// Grip-Editor-Modus nicht aktiv – reiner Attachment-Preview
|
||||||
|
input.gripEditorActive = false;
|
||||||
|
input.gripEditorIsShield = false;
|
||||||
|
input.gripEditorCharPreview = false;
|
||||||
|
input.attachmentEditorActive = true;
|
||||||
|
|
||||||
|
// Subkategorie + aktuelle Config in SharedInput laden
|
||||||
|
input.gripEditorSubCategory = subCategory != null ? subCategory.name() : null;
|
||||||
|
WeaponCategoryAttachment cfg = WeaponAttachmentIO.get(subCategory);
|
||||||
|
input.gripEditorCatHandOffX = cfg.getHandOffX();
|
||||||
|
input.gripEditorCatHandOffY = cfg.getHandOffY();
|
||||||
|
input.gripEditorCatHandOffZ = cfg.getHandOffZ();
|
||||||
|
input.gripEditorCatHand2OffX = cfg.getHand2OffX();
|
||||||
|
input.gripEditorCatHand2OffY = cfg.getHand2OffY();
|
||||||
|
input.gripEditorCatHand2OffZ = cfg.getHand2OffZ();
|
||||||
|
input.gripEditorCatOffsetsDirty = false;
|
||||||
|
|
||||||
|
// Grip-Werte des Items setzen, damit updateCharPreviewWeapon g1/g2 kennt
|
||||||
|
ItemGrip g = grip != null ? grip : new ItemGrip();
|
||||||
|
input.gripEditorPosX = g.getPosX() - ItemGrip.HAND_GRIP_OFFSET_X;
|
||||||
|
input.gripEditorPosY = g.getPosY() - ItemGrip.HAND_GRIP_OFFSET_Y;
|
||||||
|
input.gripEditorPosZ = g.getPosZ() - ItemGrip.HAND_GRIP_OFFSET_Z;
|
||||||
|
input.gripEditorRotX = g.getRotX();
|
||||||
|
input.gripEditorRotY = g.getRotY();
|
||||||
|
input.gripEditorRotZ = g.getRotZ();
|
||||||
|
input.gripEditorGrip2PosX = g.getGrip2PosX();
|
||||||
|
input.gripEditorGrip2PosY = g.getGrip2PosY();
|
||||||
|
input.gripEditorGrip2PosZ = g.getGrip2PosZ();
|
||||||
|
input.gripEditorGizmoDirty = false;
|
||||||
|
|
||||||
|
// Waffenmodell laden
|
||||||
|
if (modelPath != null && !modelPath.isBlank()) {
|
||||||
|
input.modelEditorOpenPath = modelPath;
|
||||||
|
}
|
||||||
|
input.activeLayer = SharedInput.LAYER_MODEL_EDITOR;
|
||||||
|
if (ctx.jmeApp() != null) ctx.jmeApp().setRenderThrottled(false);
|
||||||
|
|
||||||
|
ctx.setCenterView().accept(ctx.viewportSupplier().get());
|
||||||
|
ctx.setCurrentTool().accept("attachmentEditor");
|
||||||
|
ctx.setMainToolbar().accept(buildToolbar());
|
||||||
|
|
||||||
|
VBox innerPanel = buildPanel(cfg, subCategory);
|
||||||
|
ScrollPane scroll = new ScrollPane(innerPanel);
|
||||||
|
scroll.setFitToWidth(true);
|
||||||
|
scroll.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
|
||||||
|
scroll.setStyle("-fx-background-color:transparent;-fx-background:transparent;");
|
||||||
|
scroll.skinProperty().addListener((obs, o, n) -> {
|
||||||
|
javafx.scene.Node vp = scroll.lookup(".viewport");
|
||||||
|
if (vp != null) vp.setStyle("-fx-background-color: #252535;");
|
||||||
|
});
|
||||||
|
VBox outerPanel = new VBox(scroll);
|
||||||
|
VBox.setVgrow(scroll, Priority.ALWAYS);
|
||||||
|
outerPanel.setPrefWidth(220);
|
||||||
|
outerPanel.setMaxWidth(220);
|
||||||
|
outerPanel.setStyle("-fx-background-color: #252535;");
|
||||||
|
ctx.setRightPanel().accept(outerPanel);
|
||||||
|
ctx.setLeftPanel().accept(null);
|
||||||
|
ctx.setBottomPanel().accept(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Toolbar ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
private ToolBar buildToolbar() {
|
||||||
|
Button backBtn = new Button("<- Zurück");
|
||||||
|
backBtn.setOnAction(e -> onCancel());
|
||||||
|
Label title = new Label("Hand-Anker-Editor");
|
||||||
|
title.setStyle("-fx-font-weight: bold; -fx-font-size: 13;");
|
||||||
|
ToolBar tb = new ToolBar();
|
||||||
|
tb.getItems().addAll(backBtn, new Separator(Orientation.VERTICAL), title);
|
||||||
|
return tb;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Panel ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
private VBox buildPanel(WeaponCategoryAttachment cfg, ItemSubCategory subCategory) {
|
||||||
|
handOffXSpin = doubleSpin(cfg.getHandOffX(), -2.0, 2.0, 0.005, "m");
|
||||||
|
handOffYSpin = doubleSpin(cfg.getHandOffY(), -2.0, 2.0, 0.005, "m");
|
||||||
|
handOffZSpin = doubleSpin(cfg.getHandOffZ(), -2.0, 2.0, 0.005, "m");
|
||||||
|
hand2OffXSpin = doubleSpin(cfg.getHand2OffX(), -2.0, 2.0, 0.005, "m");
|
||||||
|
hand2OffYSpin = doubleSpin(cfg.getHand2OffY(), -2.0, 2.0, 0.005, "m");
|
||||||
|
hand2OffZSpin = doubleSpin(cfg.getHand2OffZ(), -2.0, 2.0, 0.005, "m");
|
||||||
|
|
||||||
|
handOffXSpin.valueProperty().addListener((obs, o, n) -> onSpinnerChanged());
|
||||||
|
handOffYSpin.valueProperty().addListener((obs, o, n) -> onSpinnerChanged());
|
||||||
|
handOffZSpin.valueProperty().addListener((obs, o, n) -> onSpinnerChanged());
|
||||||
|
hand2OffXSpin.valueProperty().addListener((obs, o, n) -> onSpinnerChanged());
|
||||||
|
hand2OffYSpin.valueProperty().addListener((obs, o, n) -> onSpinnerChanged());
|
||||||
|
hand2OffZSpin.valueProperty().addListener((obs, o, n) -> onSpinnerChanged());
|
||||||
|
|
||||||
|
Button saveBtn = new Button("Speichern");
|
||||||
|
saveBtn.setMaxWidth(Double.MAX_VALUE);
|
||||||
|
saveBtn.setStyle("-fx-background-color: #2a5a2a; -fx-text-fill: #aaffaa; -fx-cursor: hand; -fx-font-weight: bold;");
|
||||||
|
saveBtn.setOnAction(e -> saveAndReturn());
|
||||||
|
|
||||||
|
Button cancelBtn = new Button("Abbrechen");
|
||||||
|
cancelBtn.setMaxWidth(Double.MAX_VALUE);
|
||||||
|
cancelBtn.setStyle("-fx-background-color: #3a3a3a; -fx-text-fill: #cccccc; -fx-cursor: hand;");
|
||||||
|
cancelBtn.setOnAction(e -> onCancel());
|
||||||
|
|
||||||
|
String catLabel = subCategory != null ? subCategory.name() : "–";
|
||||||
|
|
||||||
|
VBox panel = new VBox(8);
|
||||||
|
panel.setPadding(new Insets(12));
|
||||||
|
panel.setStyle("-fx-background-color: #252535;");
|
||||||
|
panel.setPrefWidth(220);
|
||||||
|
panel.getChildren().addAll(
|
||||||
|
styledTitle("Kategorie: " + catLabel),
|
||||||
|
styledHint("Offsets gelten für alle Waffen dieser Kategorie.\nDer Held zeigt die Waffe in der Idle-Animation."),
|
||||||
|
new Separator(),
|
||||||
|
styledTitle("Rechte Hand – Anker-Offset (m)"),
|
||||||
|
styledHint("Verschiebt den Befestigungspunkt der rechten Hand\nim Knochenraum des Charakters."),
|
||||||
|
row("X:", handOffXSpin),
|
||||||
|
row("Y:", handOffYSpin),
|
||||||
|
row("Z:", handOffZSpin),
|
||||||
|
new Separator(),
|
||||||
|
styledTitle("Linke Hand – Anker-Offset (m)"),
|
||||||
|
styledHint("Zweiter Griffpunkt (linke Hand) – nur relevant bei\nZweihänder-Waffen."),
|
||||||
|
row("X:", hand2OffXSpin),
|
||||||
|
row("Y:", hand2OffYSpin),
|
||||||
|
row("Z:", hand2OffZSpin),
|
||||||
|
new Separator(),
|
||||||
|
saveBtn,
|
||||||
|
cancelBtn
|
||||||
|
);
|
||||||
|
return panel;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Spinner-Events ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
private void onSpinnerChanged() {
|
||||||
|
if (suppressEvents) return;
|
||||||
|
SharedInput input = ctx.input();
|
||||||
|
input.gripEditorCatHandOffX = handOffXSpin.getValue().floatValue();
|
||||||
|
input.gripEditorCatHandOffY = handOffYSpin.getValue().floatValue();
|
||||||
|
input.gripEditorCatHandOffZ = handOffZSpin.getValue().floatValue();
|
||||||
|
input.gripEditorCatHand2OffX = hand2OffXSpin.getValue().floatValue();
|
||||||
|
input.gripEditorCatHand2OffY = hand2OffYSpin.getValue().floatValue();
|
||||||
|
input.gripEditorCatHand2OffZ = hand2OffZSpin.getValue().floatValue();
|
||||||
|
input.gripEditorCatOffsetsDirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Speichern / Abbrechen ─────────────────────────────────────────────────
|
||||||
|
|
||||||
|
private void saveAndReturn() {
|
||||||
|
if (itemSubCategory != null) {
|
||||||
|
WeaponCategoryAttachment cfg = new WeaponCategoryAttachment();
|
||||||
|
cfg.setHandOffX(handOffXSpin.getValue().floatValue());
|
||||||
|
cfg.setHandOffY(handOffYSpin.getValue().floatValue());
|
||||||
|
cfg.setHandOffZ(handOffZSpin.getValue().floatValue());
|
||||||
|
cfg.setHand2OffX(hand2OffXSpin.getValue().floatValue());
|
||||||
|
cfg.setHand2OffY(hand2OffYSpin.getValue().floatValue());
|
||||||
|
cfg.setHand2OffZ(hand2OffZSpin.getValue().floatValue());
|
||||||
|
WeaponAttachmentIO.set(itemSubCategory, cfg);
|
||||||
|
java.nio.file.Path dest = ctx.assetRoot().resolve("weapon_attachment.json");
|
||||||
|
WeaponAttachmentIO.save(dest);
|
||||||
|
syncToMirrors(dest);
|
||||||
|
}
|
||||||
|
closeEditor();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void syncToMirrors(java.nio.file.Path savedFile) {
|
||||||
|
java.nio.file.Path projectRoot = de.blight.editor.ProjectRoot.PATH;
|
||||||
|
String[] mirrorRoots = {
|
||||||
|
"blight-assets/bin/main",
|
||||||
|
"blight-assets/build/resources/main"
|
||||||
|
};
|
||||||
|
for (String root : mirrorRoots) {
|
||||||
|
java.nio.file.Path mirrorRoot = projectRoot.resolve(root);
|
||||||
|
if (!java.nio.file.Files.isDirectory(mirrorRoot)) continue;
|
||||||
|
java.nio.file.Path mirror = mirrorRoot.resolve("weapon_attachment.json");
|
||||||
|
try {
|
||||||
|
java.nio.file.Files.copy(savedFile, mirror,
|
||||||
|
java.nio.file.StandardCopyOption.REPLACE_EXISTING);
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Mirror nicht vorhanden oder nicht beschreibbar – ignorieren
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onCancel() {
|
||||||
|
closeEditor();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void closeEditor() {
|
||||||
|
SharedInput input = ctx.input();
|
||||||
|
input.attachmentEditorActive = false;
|
||||||
|
input.gripEditorCharPreview = false;
|
||||||
|
input.gripEditorCatOffsetsDirty = false;
|
||||||
|
|
||||||
|
Runnable ret = onReturn;
|
||||||
|
onReturn = null;
|
||||||
|
itemSubCategory = null;
|
||||||
|
|
||||||
|
if (ret != null) ret.run(); else ctx.switchToWorld().run();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Helpers ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
private static Spinner<Double> doubleSpin(float initial, double min, double max, double step, String unit) {
|
||||||
|
SpinnerValueFactory.DoubleSpinnerValueFactory vf =
|
||||||
|
new SpinnerValueFactory.DoubleSpinnerValueFactory(min, max, initial, step);
|
||||||
|
vf.setConverter(new javafx.util.StringConverter<>() {
|
||||||
|
@Override public String toString(Double v) { return v == null ? "0" : String.format("%.3f", v) + " " + unit; }
|
||||||
|
@Override public Double fromString(String s) {
|
||||||
|
try { return Double.parseDouble(s.replace(unit, "").replace(",", ".").trim()); }
|
||||||
|
catch (NumberFormatException e) { return vf.getValue(); }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Spinner<Double> sp = new Spinner<>(vf);
|
||||||
|
sp.setEditable(true);
|
||||||
|
sp.setMaxWidth(Double.MAX_VALUE);
|
||||||
|
return sp;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Label styledTitle(String text) {
|
||||||
|
Label l = new Label(text);
|
||||||
|
l.setStyle("-fx-font-weight: bold; -fx-font-size: 11; -fx-text-fill: #88aacc;");
|
||||||
|
l.setWrapText(true);
|
||||||
|
return l;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Label styledHint(String text) {
|
||||||
|
Label l = new Label(text);
|
||||||
|
l.setWrapText(true);
|
||||||
|
l.setStyle("-fx-text-fill: #888; -fx-font-size: 10;");
|
||||||
|
return l;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static HBox row(String labelText, Spinner<Double> spinner) {
|
||||||
|
Label lbl = new Label(labelText);
|
||||||
|
lbl.setMinWidth(30);
|
||||||
|
lbl.setStyle("-fx-text-fill: #aaa;");
|
||||||
|
HBox.setHgrow(spinner, Priority.ALWAYS);
|
||||||
|
HBox box = new HBox(6, lbl, spinner);
|
||||||
|
box.setAlignment(Pos.CENTER_LEFT);
|
||||||
|
return box;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -89,10 +89,14 @@ public class ContentController {
|
|||||||
|
|
||||||
private Runnable onEnterContentMode;
|
private Runnable onEnterContentMode;
|
||||||
private GripEditorController gripEditorController;
|
private GripEditorController gripEditorController;
|
||||||
|
private AttachmentEditorController attachmentEditorController;
|
||||||
|
private ShieldAttachmentEditorController shieldAttachmentEditorController;
|
||||||
|
|
||||||
public ContentController(ToolContext ctx) {
|
public ContentController(ToolContext ctx) {
|
||||||
this.ctx = ctx;
|
this.ctx = ctx;
|
||||||
this.gripEditorController = new GripEditorController(ctx);
|
this.gripEditorController = new GripEditorController(ctx);
|
||||||
|
this.attachmentEditorController = new AttachmentEditorController(ctx);
|
||||||
|
this.shieldAttachmentEditorController = new ShieldAttachmentEditorController(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Öffentliche API ────────────────────────────────────────────────────────
|
// ── Öffentliche API ────────────────────────────────────────────────────────
|
||||||
@@ -372,7 +376,10 @@ public class ContentController {
|
|||||||
wireItemView(activeItemView);
|
wireItemView(activeItemView);
|
||||||
ctx.setCenterView().accept(activeItemView);
|
ctx.setCenterView().accept(activeItemView);
|
||||||
ctx.setRightPanel().accept(null);
|
ctx.setRightPanel().accept(null);
|
||||||
if (selectItemId != null) activeItemView.selectItem(selectItemId);
|
if (selectItemId != null) {
|
||||||
|
activeItemView.selectItem(selectItemId);
|
||||||
|
selectItemInTree(selectItemId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Prefs-Restore: stellt Dual-Window-Position und Content-Section wieder her. */
|
/** Prefs-Restore: stellt Dual-Window-Position und Content-Section wieder her. */
|
||||||
@@ -995,7 +1002,7 @@ public class ContentController {
|
|||||||
|
|
||||||
private void wireItemView(de.blight.editor.ui.ItemEditorView view) {
|
private void wireItemView(de.blight.editor.ui.ItemEditorView view) {
|
||||||
view.setOnIdChanged(this::refreshContentItemTree);
|
view.setOnIdChanged(this::refreshContentItemTree);
|
||||||
view.setGripEditorLauncher((modelPath, currentGrip, isShield, onSave) -> {
|
view.setGripEditorLauncher((modelPath, currentGrip, isShield, subCategory, onSave) -> {
|
||||||
String itemId = view.getCurrentItemId();
|
String itemId = view.getCurrentItemId();
|
||||||
gripEditorController.setOnReturn(() -> {
|
gripEditorController.setOnReturn(() -> {
|
||||||
if (dualWindowMode) {
|
if (dualWindowMode) {
|
||||||
@@ -1005,7 +1012,31 @@ public class ContentController {
|
|||||||
switchToItemEditor(itemId);
|
switchToItemEditor(itemId);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
gripEditorController.open(modelPath, currentGrip, isShield, onSave);
|
gripEditorController.open(modelPath, currentGrip, isShield, subCategory, onSave);
|
||||||
|
});
|
||||||
|
view.setAttachmentEditorLauncher((modelPath, subCategory, grip) -> {
|
||||||
|
String itemId = view.getCurrentItemId();
|
||||||
|
attachmentEditorController.setOnReturn(() -> {
|
||||||
|
if (dualWindowMode) {
|
||||||
|
buildItemViewInContent();
|
||||||
|
if (activeItemView != null) activeItemView.selectItem(itemId);
|
||||||
|
} else {
|
||||||
|
switchToItemEditor(itemId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
attachmentEditorController.open(modelPath, subCategory, grip);
|
||||||
|
});
|
||||||
|
view.setShieldAttachmentEditorLauncher((modelPath, subCategory, grip) -> {
|
||||||
|
String itemId = view.getCurrentItemId();
|
||||||
|
shieldAttachmentEditorController.setOnReturn(() -> {
|
||||||
|
if (dualWindowMode) {
|
||||||
|
buildItemViewInContent();
|
||||||
|
if (activeItemView != null) activeItemView.selectItem(itemId);
|
||||||
|
} else {
|
||||||
|
switchToItemEditor(itemId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
shieldAttachmentEditorController.open(modelPath, subCategory, grip);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1693,6 +1724,25 @@ public class ContentController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void selectItemInTree(String itemId) {
|
||||||
|
if (contentItemNode == null || contentTreeView == null || itemId == null) return;
|
||||||
|
contentItemNode.setExpanded(true);
|
||||||
|
for (TreeItem<String> leaf : contentItemNode.getChildren()) {
|
||||||
|
String val = leaf.getValue();
|
||||||
|
String leafId = val != null && val.contains(":") ? val.substring(val.indexOf(':') + 1) : val;
|
||||||
|
if (itemId.equals(leafId)) {
|
||||||
|
contentTreeUpdating = true;
|
||||||
|
try {
|
||||||
|
contentTreeView.getSelectionModel().select(leaf);
|
||||||
|
contentTreeView.scrollTo(contentTreeView.getRow(leaf));
|
||||||
|
} finally {
|
||||||
|
contentTreeUpdating = false;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void refreshContentFractionTree() {
|
private void refreshContentFractionTree() {
|
||||||
if (contentFractionNode == null) return;
|
if (contentFractionNode == null) return;
|
||||||
contentTreeUpdating = true;
|
contentTreeUpdating = true;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package de.blight.editor.toolset.content;
|
package de.blight.editor.toolset.content;
|
||||||
|
|
||||||
import de.blight.common.model.ItemGrip;
|
import de.blight.common.model.ItemGrip;
|
||||||
|
import de.blight.common.model.ItemSubCategory;
|
||||||
import de.blight.editor.SharedInput;
|
import de.blight.editor.SharedInput;
|
||||||
import de.blight.editor.ToolContext;
|
import de.blight.editor.ToolContext;
|
||||||
import de.blight.editor.state.ModelEditorState;
|
import de.blight.editor.state.ModelEditorState;
|
||||||
@@ -30,6 +31,7 @@ public class GripEditorController implements ItemEditorView.GripEditorLauncher {
|
|||||||
private Runnable onReturn;
|
private Runnable onReturn;
|
||||||
private boolean suppressSpinnerEvents = false;
|
private boolean suppressSpinnerEvents = false;
|
||||||
private boolean shieldMode = false;
|
private boolean shieldMode = false;
|
||||||
|
private ItemSubCategory itemSubCategory = null;
|
||||||
|
|
||||||
public void setOnReturn(Runnable r) { this.onReturn = r; }
|
public void setOnReturn(Runnable r) { this.onReturn = r; }
|
||||||
|
|
||||||
@@ -47,12 +49,13 @@ public class GripEditorController implements ItemEditorView.GripEditorLauncher {
|
|||||||
rotXSpin.getValueFactory().setValue(0.0);
|
rotXSpin.getValueFactory().setValue(0.0);
|
||||||
rotYSpin.getValueFactory().setValue(0.0);
|
rotYSpin.getValueFactory().setValue(0.0);
|
||||||
rotZSpin.getValueFactory().setValue(0.0);
|
rotZSpin.getValueFactory().setValue(0.0);
|
||||||
// 2. Griff (linke Hand): 20cm Richtung Pommel vom 1. Griff (Zweihänder-Grifflänge).
|
// 2. Griff (linke Hand) nur für Zweihänder setzen
|
||||||
// Grip2-Spinner = Modell-Koordinaten direkt (kein HAND_GRIP_OFFSET), deshalb JSON-Pos von Grip1 verwenden.
|
if (itemSubCategory == ItemSubCategory.TWO_HANDED_SWORD) {
|
||||||
float grip1JsonZ = posZ + ItemGrip.HAND_GRIP_OFFSET_Z;
|
float grip1JsonZ = posZ + ItemGrip.HAND_GRIP_OFFSET_Z;
|
||||||
grip2XSpin.getValueFactory().setValue((double) gX);
|
grip2XSpin.getValueFactory().setValue((double) gX);
|
||||||
grip2YSpin.getValueFactory().setValue((double) gY);
|
grip2YSpin.getValueFactory().setValue((double) gY);
|
||||||
grip2ZSpin.getValueFactory().setValue((double)(grip1JsonZ - bladeDirZ * 0.20f));
|
grip2ZSpin.getValueFactory().setValue((double)(grip1JsonZ - bladeDirZ * 0.20f));
|
||||||
|
}
|
||||||
suppressSpinnerEvents = false;
|
suppressSpinnerEvents = false;
|
||||||
onSpinnerChanged();
|
onSpinnerChanged();
|
||||||
}
|
}
|
||||||
@@ -64,13 +67,19 @@ public class GripEditorController implements ItemEditorView.GripEditorLauncher {
|
|||||||
// ── GripEditorLauncher ────────────────────────────────────────────────────
|
// ── GripEditorLauncher ────────────────────────────────────────────────────
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void open(String modelPath, ItemGrip currentGrip, boolean isShield, Consumer<ItemGrip> onSave) {
|
public void open(String modelPath, ItemGrip currentGrip, boolean isShield,
|
||||||
|
ItemSubCategory subCategory, Consumer<ItemGrip> onSave) {
|
||||||
pendingOnSave = onSave;
|
pendingOnSave = onSave;
|
||||||
shieldMode = isShield;
|
shieldMode = isShield;
|
||||||
|
itemSubCategory = subCategory;
|
||||||
|
|
||||||
SharedInput input = ctx.input();
|
SharedInput input = ctx.input();
|
||||||
input.gripEditorActive = true;
|
input.gripEditorActive = true;
|
||||||
input.gripEditorIsShield = isShield;
|
input.gripEditorIsShield = isShield;
|
||||||
|
input.gripEditorSubCategory = subCategory != null ? subCategory.name() : null;
|
||||||
|
input.gripEditorCharPreview = false;
|
||||||
|
input.gripEditorCharPreviewReady = false;
|
||||||
|
input.attachmentEditorActive = false;
|
||||||
|
|
||||||
// Grip-Werte aus Item übernehmen (oder Defaults)
|
// Grip-Werte aus Item übernehmen (oder Defaults)
|
||||||
// Spinner/Gizmo zeigen den Handflächen-Kontaktpunkt (= JSON-pos minus HAND_GRIP_OFFSET)
|
// Spinner/Gizmo zeigen den Handflächen-Kontaktpunkt (= JSON-pos minus HAND_GRIP_OFFSET)
|
||||||
@@ -125,7 +134,7 @@ public class GripEditorController implements ItemEditorView.GripEditorLauncher {
|
|||||||
// ── Toolbar ───────────────────────────────────────────────────────────────
|
// ── Toolbar ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
private ToolBar buildToolbar() {
|
private ToolBar buildToolbar() {
|
||||||
Button cancelBtn = new Button("← Zurück");
|
Button cancelBtn = new Button("<- Zurück");
|
||||||
cancelBtn.setOnAction(e -> onCancel());
|
cancelBtn.setOnAction(e -> onCancel());
|
||||||
Label title = new Label("Griff-Editor");
|
Label title = new Label("Griff-Editor");
|
||||||
title.setStyle("-fx-font-weight: bold; -fx-font-size: 13;");
|
title.setStyle("-fx-font-weight: bold; -fx-font-size: 13;");
|
||||||
@@ -286,6 +295,7 @@ public class GripEditorController implements ItemEditorView.GripEditorLauncher {
|
|||||||
SharedInput input = ctx.input();
|
SharedInput input = ctx.input();
|
||||||
input.gripEditorActive = false;
|
input.gripEditorActive = false;
|
||||||
input.gripEditorIsShield = false;
|
input.gripEditorIsShield = false;
|
||||||
|
input.gripEditorCharPreview = false;
|
||||||
|
|
||||||
Consumer<ItemGrip> save = pendingOnSave;
|
Consumer<ItemGrip> save = pendingOnSave;
|
||||||
Runnable ret = onReturn;
|
Runnable ret = onReturn;
|
||||||
@@ -297,8 +307,10 @@ public class GripEditorController implements ItemEditorView.GripEditorLauncher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void onCancel() {
|
private void onCancel() {
|
||||||
ctx.input().gripEditorActive = false;
|
SharedInput input = ctx.input();
|
||||||
ctx.input().gripEditorIsShield = false;
|
input.gripEditorActive = false;
|
||||||
|
input.gripEditorIsShield = false;
|
||||||
|
input.gripEditorCharPreview = false;
|
||||||
|
|
||||||
Runnable ret = onReturn;
|
Runnable ret = onReturn;
|
||||||
pendingOnSave = null;
|
pendingOnSave = null;
|
||||||
@@ -313,7 +325,7 @@ public class GripEditorController implements ItemEditorView.GripEditorLauncher {
|
|||||||
SpinnerValueFactory.DoubleSpinnerValueFactory vf =
|
SpinnerValueFactory.DoubleSpinnerValueFactory vf =
|
||||||
new SpinnerValueFactory.DoubleSpinnerValueFactory(min, max, initial, step);
|
new SpinnerValueFactory.DoubleSpinnerValueFactory(min, max, initial, step);
|
||||||
vf.setConverter(new javafx.util.StringConverter<>() {
|
vf.setConverter(new javafx.util.StringConverter<>() {
|
||||||
@Override public String toString(Double v) { return v == null ? "0" : String.format("%.2f", v) + " " + unit; }
|
@Override public String toString(Double v) { return v == null ? "0" : String.format("%.3f", v) + " " + unit; }
|
||||||
@Override public Double fromString(String s) {
|
@Override public Double fromString(String s) {
|
||||||
try { return Double.parseDouble(s.replace(unit, "").replace(",", ".").trim()); }
|
try { return Double.parseDouble(s.replace(unit, "").replace(",", ".").trim()); }
|
||||||
catch (NumberFormatException e) { return vf.getValue(); }
|
catch (NumberFormatException e) { return vf.getValue(); }
|
||||||
@@ -340,7 +352,7 @@ public class GripEditorController implements ItemEditorView.GripEditorLauncher {
|
|||||||
|
|
||||||
private static HBox row(String labelText, Spinner<Double> spinner) {
|
private static HBox row(String labelText, Spinner<Double> spinner) {
|
||||||
Label lbl = new Label(labelText);
|
Label lbl = new Label(labelText);
|
||||||
lbl.setMinWidth(20);
|
lbl.setMinWidth(30);
|
||||||
lbl.setStyle("-fx-text-fill: #aaa;");
|
lbl.setStyle("-fx-text-fill: #aaa;");
|
||||||
HBox.setHgrow(spinner, Priority.ALWAYS);
|
HBox.setHgrow(spinner, Priority.ALWAYS);
|
||||||
HBox box = new HBox(6, lbl, spinner);
|
HBox box = new HBox(6, lbl, spinner);
|
||||||
|
|||||||
@@ -0,0 +1,253 @@
|
|||||||
|
package de.blight.editor.toolset.content;
|
||||||
|
|
||||||
|
import de.blight.common.model.ItemGrip;
|
||||||
|
import de.blight.common.model.ItemSubCategory;
|
||||||
|
import de.blight.common.model.ShieldAttachmentIO;
|
||||||
|
import de.blight.common.model.ShieldCategoryAttachment;
|
||||||
|
import de.blight.editor.SharedInput;
|
||||||
|
import de.blight.editor.ToolContext;
|
||||||
|
import javafx.geometry.Insets;
|
||||||
|
import javafx.geometry.Orientation;
|
||||||
|
import javafx.geometry.Pos;
|
||||||
|
import javafx.scene.control.*;
|
||||||
|
import javafx.scene.layout.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Eigenständige Ansicht zum Bearbeiten der Schild-Arm-Attachment-Parameter pro Kategorie.
|
||||||
|
* Zeigt den Helden in Sword-And-Shield Idle mit Schild am linken Arm.
|
||||||
|
*/
|
||||||
|
public class ShieldAttachmentEditorController {
|
||||||
|
|
||||||
|
private final ToolContext ctx;
|
||||||
|
|
||||||
|
private Spinner<Double> rotXSpin, rotYSpin, rotZSpin, elbowOffsetSpin;
|
||||||
|
|
||||||
|
private boolean suppressEvents = false;
|
||||||
|
private ItemSubCategory itemSubCategory = null;
|
||||||
|
private Runnable onReturn = null;
|
||||||
|
|
||||||
|
public ShieldAttachmentEditorController(ToolContext ctx) {
|
||||||
|
this.ctx = ctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnReturn(Runnable r) { this.onReturn = r; }
|
||||||
|
|
||||||
|
// ── Öffnen ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
public void open(String modelPath, ItemSubCategory subCategory, ItemGrip grip) {
|
||||||
|
itemSubCategory = subCategory;
|
||||||
|
|
||||||
|
SharedInput input = ctx.input();
|
||||||
|
input.gripEditorActive = false;
|
||||||
|
input.gripEditorCharPreview = false;
|
||||||
|
input.attachmentEditorActive = false;
|
||||||
|
input.shieldEditorActive = true;
|
||||||
|
|
||||||
|
input.shieldEditorSubCategory = subCategory != null ? subCategory.name() : null;
|
||||||
|
ShieldCategoryAttachment cfg = ShieldAttachmentIO.get(subCategory);
|
||||||
|
input.shieldEditorRotX = cfg.getRotX();
|
||||||
|
input.shieldEditorRotY = cfg.getRotY();
|
||||||
|
input.shieldEditorRotZ = cfg.getRotZ();
|
||||||
|
input.shieldEditorElbowOffset = cfg.getElbowOffset();
|
||||||
|
input.shieldEditorOffsetsDirty = false;
|
||||||
|
|
||||||
|
// Grip des Schilds für Preview-Positionierung
|
||||||
|
ItemGrip g = grip != null ? grip : new ItemGrip();
|
||||||
|
input.shieldEditorGripX = g.getPosX();
|
||||||
|
input.shieldEditorGripY = g.getPosY();
|
||||||
|
input.shieldEditorGripZ = g.getPosZ();
|
||||||
|
|
||||||
|
if (modelPath != null && !modelPath.isBlank()) {
|
||||||
|
input.modelEditorOpenPath = modelPath;
|
||||||
|
}
|
||||||
|
input.activeLayer = SharedInput.LAYER_MODEL_EDITOR;
|
||||||
|
if (ctx.jmeApp() != null) ctx.jmeApp().setRenderThrottled(false);
|
||||||
|
|
||||||
|
ctx.setCenterView().accept(ctx.viewportSupplier().get());
|
||||||
|
ctx.setCurrentTool().accept("shieldAttachmentEditor");
|
||||||
|
ctx.setMainToolbar().accept(buildToolbar());
|
||||||
|
|
||||||
|
VBox innerPanel = buildPanel(cfg, subCategory);
|
||||||
|
ScrollPane scroll = new ScrollPane(innerPanel);
|
||||||
|
scroll.setFitToWidth(true);
|
||||||
|
scroll.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
|
||||||
|
scroll.setStyle("-fx-background-color:transparent;-fx-background:transparent;");
|
||||||
|
scroll.skinProperty().addListener((obs, o, n) -> {
|
||||||
|
javafx.scene.Node vp = scroll.lookup(".viewport");
|
||||||
|
if (vp != null) vp.setStyle("-fx-background-color: #252535;");
|
||||||
|
});
|
||||||
|
VBox outerPanel = new VBox(scroll);
|
||||||
|
VBox.setVgrow(scroll, Priority.ALWAYS);
|
||||||
|
outerPanel.setPrefWidth(220);
|
||||||
|
outerPanel.setMaxWidth(220);
|
||||||
|
outerPanel.setStyle("-fx-background-color: #252535;");
|
||||||
|
ctx.setRightPanel().accept(outerPanel);
|
||||||
|
ctx.setLeftPanel().accept(null);
|
||||||
|
ctx.setBottomPanel().accept(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Toolbar ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
private ToolBar buildToolbar() {
|
||||||
|
Button backBtn = new Button("<- Zurück");
|
||||||
|
backBtn.setOnAction(e -> onCancel());
|
||||||
|
Label title = new Label("Schild-Anker-Editor");
|
||||||
|
title.setStyle("-fx-font-weight: bold; -fx-font-size: 13;");
|
||||||
|
ToolBar tb = new ToolBar();
|
||||||
|
tb.getItems().addAll(backBtn, new Separator(Orientation.VERTICAL), title);
|
||||||
|
return tb;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Panel ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
private VBox buildPanel(ShieldCategoryAttachment cfg, ItemSubCategory subCategory) {
|
||||||
|
rotXSpin = doubleSpin(cfg.getRotX(), -360.0, 360.0, 1.0, "°");
|
||||||
|
rotYSpin = doubleSpin(cfg.getRotY(), -360.0, 360.0, 1.0, "°");
|
||||||
|
rotZSpin = doubleSpin(cfg.getRotZ(), -360.0, 360.0, 1.0, "°");
|
||||||
|
elbowOffsetSpin = doubleSpin(cfg.getElbowOffset(), 0.0, 1.0, 0.005, "m");
|
||||||
|
|
||||||
|
rotXSpin.valueProperty().addListener((obs, o, n) -> onSpinnerChanged());
|
||||||
|
rotYSpin.valueProperty().addListener((obs, o, n) -> onSpinnerChanged());
|
||||||
|
rotZSpin.valueProperty().addListener((obs, o, n) -> onSpinnerChanged());
|
||||||
|
elbowOffsetSpin.valueProperty().addListener((obs, o, n) -> onSpinnerChanged());
|
||||||
|
|
||||||
|
Button saveBtn = new Button("Speichern");
|
||||||
|
saveBtn.setMaxWidth(Double.MAX_VALUE);
|
||||||
|
saveBtn.setStyle("-fx-background-color: #2a5a2a; -fx-text-fill: #aaffaa; -fx-cursor: hand; -fx-font-weight: bold;");
|
||||||
|
saveBtn.setOnAction(e -> saveAndReturn());
|
||||||
|
|
||||||
|
Button cancelBtn = new Button("Abbrechen");
|
||||||
|
cancelBtn.setMaxWidth(Double.MAX_VALUE);
|
||||||
|
cancelBtn.setStyle("-fx-background-color: #3a3a3a; -fx-text-fill: #cccccc; -fx-cursor: hand;");
|
||||||
|
cancelBtn.setOnAction(e -> onCancel());
|
||||||
|
|
||||||
|
String catLabel = subCategory != null ? subCategory.name() : "–";
|
||||||
|
|
||||||
|
VBox panel = new VBox(8);
|
||||||
|
panel.setPadding(new Insets(12));
|
||||||
|
panel.setStyle("-fx-background-color: #252535;");
|
||||||
|
panel.setPrefWidth(220);
|
||||||
|
panel.getChildren().addAll(
|
||||||
|
styledTitle("Kategorie: " + catLabel),
|
||||||
|
styledHint("Steuert wie das Schild am linken Unterarm angebracht ist.\nDer Held zeigt die Pose in der Sword-And-Shield Idle-Animation."),
|
||||||
|
new Separator(),
|
||||||
|
styledTitle("Rotation am Arm (Grad)"),
|
||||||
|
styledHint("Dreht das Schild relativ zur Bind-Pose des LeftHand-Knochens."),
|
||||||
|
row("X:", rotXSpin),
|
||||||
|
row("Y:", rotYSpin),
|
||||||
|
row("Z:", rotZSpin),
|
||||||
|
new Separator(),
|
||||||
|
styledTitle("Ellbogen-Offset (m)"),
|
||||||
|
styledHint("Verschiebung in Richtung Ellbogen (lokale -Y des LeftHand-Knochens)."),
|
||||||
|
row("Off:", elbowOffsetSpin),
|
||||||
|
new Separator(),
|
||||||
|
saveBtn,
|
||||||
|
cancelBtn
|
||||||
|
);
|
||||||
|
return panel;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Spinner-Events ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
private void onSpinnerChanged() {
|
||||||
|
if (suppressEvents) return;
|
||||||
|
SharedInput input = ctx.input();
|
||||||
|
input.shieldEditorRotX = rotXSpin.getValue().floatValue();
|
||||||
|
input.shieldEditorRotY = rotYSpin.getValue().floatValue();
|
||||||
|
input.shieldEditorRotZ = rotZSpin.getValue().floatValue();
|
||||||
|
input.shieldEditorElbowOffset = elbowOffsetSpin.getValue().floatValue();
|
||||||
|
input.shieldEditorOffsetsDirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Speichern / Abbrechen ─────────────────────────────────────────────────
|
||||||
|
|
||||||
|
private void saveAndReturn() {
|
||||||
|
if (itemSubCategory != null) {
|
||||||
|
ShieldCategoryAttachment cfg = new ShieldCategoryAttachment();
|
||||||
|
cfg.setRotX(rotXSpin.getValue().floatValue());
|
||||||
|
cfg.setRotY(rotYSpin.getValue().floatValue());
|
||||||
|
cfg.setRotZ(rotZSpin.getValue().floatValue());
|
||||||
|
cfg.setElbowOffset(elbowOffsetSpin.getValue().floatValue());
|
||||||
|
ShieldAttachmentIO.set(itemSubCategory, cfg);
|
||||||
|
java.nio.file.Path dest = ctx.assetRoot().resolve("shield_attachment.json");
|
||||||
|
ShieldAttachmentIO.save(dest);
|
||||||
|
syncToMirrors(dest);
|
||||||
|
}
|
||||||
|
closeEditor();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void syncToMirrors(java.nio.file.Path savedFile) {
|
||||||
|
java.nio.file.Path projectRoot = de.blight.editor.ProjectRoot.PATH;
|
||||||
|
String[] mirrorRoots = {
|
||||||
|
"blight-assets/bin/main",
|
||||||
|
"blight-assets/build/resources/main"
|
||||||
|
};
|
||||||
|
for (String root : mirrorRoots) {
|
||||||
|
java.nio.file.Path mirror = projectRoot.resolve(root).resolve("shield_attachment.json");
|
||||||
|
try {
|
||||||
|
java.nio.file.Files.copy(savedFile, mirror,
|
||||||
|
java.nio.file.StandardCopyOption.REPLACE_EXISTING);
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Mirror nicht vorhanden – ignorieren
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onCancel() {
|
||||||
|
closeEditor();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void closeEditor() {
|
||||||
|
SharedInput input = ctx.input();
|
||||||
|
input.shieldEditorActive = false;
|
||||||
|
input.shieldEditorOffsetsDirty = false;
|
||||||
|
|
||||||
|
Runnable ret = onReturn;
|
||||||
|
onReturn = null;
|
||||||
|
itemSubCategory = null;
|
||||||
|
|
||||||
|
if (ret != null) ret.run(); else ctx.switchToWorld().run();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Helpers ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
private static Spinner<Double> doubleSpin(float initial, double min, double max, double step, String unit) {
|
||||||
|
SpinnerValueFactory.DoubleSpinnerValueFactory vf =
|
||||||
|
new SpinnerValueFactory.DoubleSpinnerValueFactory(min, max, initial, step);
|
||||||
|
vf.setConverter(new javafx.util.StringConverter<>() {
|
||||||
|
@Override public String toString(Double v) { return v == null ? "0" : String.format("%.3f", v) + " " + unit; }
|
||||||
|
@Override public Double fromString(String s) {
|
||||||
|
try { return Double.parseDouble(s.replace(unit, "").replace(",", ".").trim()); }
|
||||||
|
catch (NumberFormatException e) { return vf.getValue(); }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Spinner<Double> sp = new Spinner<>(vf);
|
||||||
|
sp.setEditable(true);
|
||||||
|
sp.setMaxWidth(Double.MAX_VALUE);
|
||||||
|
return sp;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Label styledTitle(String text) {
|
||||||
|
Label l = new Label(text);
|
||||||
|
l.setStyle("-fx-font-weight: bold; -fx-font-size: 11; -fx-text-fill: #88aacc;");
|
||||||
|
l.setWrapText(true);
|
||||||
|
return l;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Label styledHint(String text) {
|
||||||
|
Label l = new Label(text);
|
||||||
|
l.setWrapText(true);
|
||||||
|
l.setStyle("-fx-text-fill: #888; -fx-font-size: 10;");
|
||||||
|
return l;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static HBox row(String labelText, Spinner<Double> spinner) {
|
||||||
|
Label lbl = new Label(labelText);
|
||||||
|
lbl.setMinWidth(30);
|
||||||
|
lbl.setStyle("-fx-text-fill: #aaa;");
|
||||||
|
HBox.setHgrow(spinner, Priority.ALWAYS);
|
||||||
|
HBox box = new HBox(6, lbl, spinner);
|
||||||
|
box.setAlignment(Pos.CENTER_LEFT);
|
||||||
|
return box;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -277,12 +277,9 @@ public class ModelEditorController implements ToolController {
|
|||||||
public void openModelEditor(String relPath, Path absolutePath) {
|
public void openModelEditor(String relPath, Path absolutePath) {
|
||||||
this.pendingRelPath = relPath;
|
this.pendingRelPath = relPath;
|
||||||
this.pendingAbsPath = absolutePath;
|
this.pendingAbsPath = absolutePath;
|
||||||
if (ctx.registry().getActive() == this) {
|
ctx.switchToWorld().run();
|
||||||
activate();
|
|
||||||
} else {
|
|
||||||
ctx.registry().activate(this);
|
ctx.registry().activate(this);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/** Öffnet den Datei-Dialog für den Modell-Import. */
|
/** Öffnet den Datei-Dialog für den Modell-Import. */
|
||||||
public void openModelImport(Window owner) {
|
public void openModelImport(Window owner) {
|
||||||
|
|||||||
@@ -56,11 +56,21 @@ public class ItemEditorView extends BorderPane {
|
|||||||
private VBox effectsRows;
|
private VBox effectsRows;
|
||||||
|
|
||||||
public interface GripEditorLauncher {
|
public interface GripEditorLauncher {
|
||||||
void open(String modelPath, ItemGrip currentGrip, boolean isShield, java.util.function.Consumer<ItemGrip> onSave);
|
void open(String modelPath, ItemGrip currentGrip, boolean isShield, ItemSubCategory subCategory, java.util.function.Consumer<ItemGrip> onSave);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface AttachmentEditorLauncher {
|
||||||
|
void open(String modelPath, ItemSubCategory subCategory, ItemGrip grip);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface ShieldAttachmentEditorLauncher {
|
||||||
|
void open(String modelPath, ItemSubCategory subCategory, ItemGrip grip);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Runnable onIdChanged;
|
private Runnable onIdChanged;
|
||||||
private GripEditorLauncher gripEditorLauncher;
|
private GripEditorLauncher gripEditorLauncher;
|
||||||
|
private AttachmentEditorLauncher attachmentEditorLauncher;
|
||||||
|
private ShieldAttachmentEditorLauncher shieldAttachmentEditorLauncher;
|
||||||
|
|
||||||
private final PauseTransition savePause = new PauseTransition(Duration.millis(600));
|
private final PauseTransition savePause = new PauseTransition(Duration.millis(600));
|
||||||
|
|
||||||
@@ -90,6 +100,8 @@ public class ItemEditorView extends BorderPane {
|
|||||||
|
|
||||||
public void setOnIdChanged(Runnable r) { this.onIdChanged = r; }
|
public void setOnIdChanged(Runnable r) { this.onIdChanged = r; }
|
||||||
public void setGripEditorLauncher(GripEditorLauncher l) { this.gripEditorLauncher = l; }
|
public void setGripEditorLauncher(GripEditorLauncher l) { this.gripEditorLauncher = l; }
|
||||||
|
public void setAttachmentEditorLauncher(AttachmentEditorLauncher l) { this.attachmentEditorLauncher = l; }
|
||||||
|
public void setShieldAttachmentEditorLauncher(ShieldAttachmentEditorLauncher l) { this.shieldAttachmentEditorLauncher = l; }
|
||||||
public String getCurrentItemId() { return current != null ? current.getItemId() : null; }
|
public String getCurrentItemId() { return current != null ? current.getItemId() : null; }
|
||||||
|
|
||||||
public void selectItem(String itemId) {
|
public void selectItem(String itemId) {
|
||||||
@@ -237,11 +249,17 @@ public class ItemEditorView extends BorderPane {
|
|||||||
gripBtn.setStyle("-fx-background-color: #3a3a5a; -fx-text-fill: #ddddff; -fx-cursor: hand;");
|
gripBtn.setStyle("-fx-background-color: #3a3a5a; -fx-text-fill: #ddddff; -fx-cursor: hand;");
|
||||||
gripBtn.setOnAction(e -> openGripEditor());
|
gripBtn.setOnAction(e -> openGripEditor());
|
||||||
|
|
||||||
|
Button attachmentBtn = new Button("Hand-Anker bearbeiten...");
|
||||||
|
attachmentBtn.setMaxWidth(Double.MAX_VALUE);
|
||||||
|
attachmentBtn.setStyle("-fx-background-color: #1a3a3a; -fx-text-fill: #88ffcc; -fx-cursor: hand;");
|
||||||
|
attachmentBtn.setOnAction(e -> openAttachmentEditor());
|
||||||
|
|
||||||
weaponSection = new VBox(6,
|
weaponSection = new VBox(6,
|
||||||
new Separator(),
|
new Separator(),
|
||||||
sectionTitle("Kampfwerte"),
|
sectionTitle("Kampfwerte"),
|
||||||
row("Schaden:", damageSpinner),
|
row("Schaden:", damageSpinner),
|
||||||
gripBtn
|
gripBtn,
|
||||||
|
attachmentBtn
|
||||||
);
|
);
|
||||||
weaponSection.setVisible(false);
|
weaponSection.setVisible(false);
|
||||||
weaponSection.setManaged(false);
|
weaponSection.setManaged(false);
|
||||||
@@ -252,10 +270,16 @@ public class ItemEditorView extends BorderPane {
|
|||||||
shieldGripBtn.setStyle("-fx-background-color: #2a4a5a; -fx-text-fill: #aaddff; -fx-cursor: hand;");
|
shieldGripBtn.setStyle("-fx-background-color: #2a4a5a; -fx-text-fill: #aaddff; -fx-cursor: hand;");
|
||||||
shieldGripBtn.setOnAction(e -> openShieldGripEditor());
|
shieldGripBtn.setOnAction(e -> openShieldGripEditor());
|
||||||
|
|
||||||
|
Button shieldAttachmentBtn = new Button("Arm-Anker bearbeiten...");
|
||||||
|
shieldAttachmentBtn.setMaxWidth(Double.MAX_VALUE);
|
||||||
|
shieldAttachmentBtn.setStyle("-fx-background-color: #1a4a4a; -fx-text-fill: #aaffee; -fx-cursor: hand;");
|
||||||
|
shieldAttachmentBtn.setOnAction(e -> openShieldAttachmentEditor());
|
||||||
|
|
||||||
shieldSection = new VBox(6,
|
shieldSection = new VBox(6,
|
||||||
new Separator(),
|
new Separator(),
|
||||||
sectionTitle("Schild-Griff"),
|
sectionTitle("Schild-Griff"),
|
||||||
shieldGripBtn
|
shieldGripBtn,
|
||||||
|
shieldAttachmentBtn
|
||||||
);
|
);
|
||||||
shieldSection.setVisible(false);
|
shieldSection.setVisible(false);
|
||||||
shieldSection.setManaged(false);
|
shieldSection.setManaged(false);
|
||||||
@@ -325,21 +349,34 @@ public class ItemEditorView extends BorderPane {
|
|||||||
private void openGripEditor() {
|
private void openGripEditor() {
|
||||||
if (current == null || gripEditorLauncher == null) return;
|
if (current == null || gripEditorLauncher == null) return;
|
||||||
String modelPath = modelRefField.getText().trim();
|
String modelPath = modelRefField.getText().trim();
|
||||||
gripEditorLauncher.open(modelPath.isBlank() ? null : modelPath, current.getGrip(), false, grip -> {
|
gripEditorLauncher.open(modelPath.isBlank() ? null : modelPath, current.getGrip(), false, current.getSubCategory(), grip -> {
|
||||||
current.setGrip(grip);
|
current.setGrip(grip);
|
||||||
persistCurrent();
|
persistCurrent();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void openAttachmentEditor() {
|
||||||
|
if (current == null || attachmentEditorLauncher == null) return;
|
||||||
|
String modelPath = modelRefField.getText().trim();
|
||||||
|
attachmentEditorLauncher.open(modelPath.isBlank() ? null : modelPath, current.getSubCategory(), current.getGrip());
|
||||||
|
}
|
||||||
|
|
||||||
private void openShieldGripEditor() {
|
private void openShieldGripEditor() {
|
||||||
if (current == null || gripEditorLauncher == null) return;
|
if (current == null || gripEditorLauncher == null) return;
|
||||||
String modelPath = modelRefField.getText().trim();
|
String modelPath = modelRefField.getText().trim();
|
||||||
gripEditorLauncher.open(modelPath.isBlank() ? null : modelPath, current.getGrip(), true, grip -> {
|
gripEditorLauncher.open(modelPath.isBlank() ? null : modelPath, current.getGrip(), true, current.getSubCategory(), grip -> {
|
||||||
current.setGrip(grip);
|
current.setGrip(grip);
|
||||||
persistCurrent();
|
persistCurrent();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void openShieldAttachmentEditor() {
|
||||||
|
if (current == null || shieldAttachmentEditorLauncher == null) return;
|
||||||
|
String modelPath = modelRefField.getText().trim();
|
||||||
|
shieldAttachmentEditorLauncher.open(modelPath.isBlank() ? null : modelPath,
|
||||||
|
current.getSubCategory(), current.getGrip());
|
||||||
|
}
|
||||||
|
|
||||||
private void updateWeaponSectionVisibility() {
|
private void updateWeaponSectionVisibility() {
|
||||||
ItemCategory cat = catCombo.getValue();
|
ItemCategory cat = catCombo.getValue();
|
||||||
ItemSubCategory sub = subCatCombo.getValue();
|
ItemSubCategory sub = subCatCombo.getValue();
|
||||||
|
|||||||
@@ -12,6 +12,10 @@ import com.jme3.scene.control.AbstractControl;
|
|||||||
import de.blight.common.model.Item;
|
import de.blight.common.model.Item;
|
||||||
import de.blight.common.model.ItemGrip;
|
import de.blight.common.model.ItemGrip;
|
||||||
import de.blight.common.model.ItemSubCategory;
|
import de.blight.common.model.ItemSubCategory;
|
||||||
|
import de.blight.common.model.ShieldAttachmentIO;
|
||||||
|
import de.blight.common.model.ShieldCategoryAttachment;
|
||||||
|
import de.blight.common.model.WeaponAttachmentIO;
|
||||||
|
import de.blight.common.model.WeaponCategoryAttachment;
|
||||||
import de.blight.game.animation.RetargetingSystem;
|
import de.blight.game.animation.RetargetingSystem;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@@ -34,6 +38,7 @@ public class NpcWeaponAttachmentControl extends AbstractControl {
|
|||||||
private static final float HALF_PI = (float) Math.PI * 0.5f;
|
private static final float HALF_PI = (float) Math.PI * 0.5f;
|
||||||
|
|
||||||
private static final Vector3f BACK_GRIP_OFFSET = new Vector3f(0.0f, 0.40f, -0.13f);
|
private static final Vector3f BACK_GRIP_OFFSET = new Vector3f(0.0f, 0.40f, -0.13f);
|
||||||
|
private static final Vector3f TWO_HAND_BACK_GRIP_OFFSET = new Vector3f(0.0f, 0.40f, -0.03f);
|
||||||
private static final Vector3f HAND_GRIP_OFFSET = new Vector3f(
|
private static final Vector3f HAND_GRIP_OFFSET = new Vector3f(
|
||||||
ItemGrip.HAND_GRIP_OFFSET_X, ItemGrip.HAND_GRIP_OFFSET_Y, ItemGrip.HAND_GRIP_OFFSET_Z);
|
ItemGrip.HAND_GRIP_OFFSET_X, ItemGrip.HAND_GRIP_OFFSET_Y, ItemGrip.HAND_GRIP_OFFSET_Z);
|
||||||
private static final Vector3f SHIELD_BACK_OFFSET = new Vector3f(0.12f, -0.10f, -0.10f);
|
private static final Vector3f SHIELD_BACK_OFFSET = new Vector3f(0.12f, -0.10f, -0.10f);
|
||||||
@@ -146,18 +151,19 @@ public class NpcWeaponAttachmentControl extends AbstractControl {
|
|||||||
boolean usesTwoHand = isTwoHand && leftForeArmNode != null
|
boolean usesTwoHand = isTwoHand && leftForeArmNode != null
|
||||||
&& wGrip != null
|
&& wGrip != null
|
||||||
&& (wGrip.getGrip2PosX() != 0f || wGrip.getGrip2PosY() != 0f || wGrip.getGrip2PosZ() != 0f);
|
&& (wGrip.getGrip2PosX() != 0f || wGrip.getGrip2PosY() != 0f || wGrip.getGrip2PosZ() != 0f);
|
||||||
|
WeaponCategoryAttachment cfg = WeaponAttachmentIO.get(
|
||||||
|
weaponItem != null ? weaponItem.getSubCategory() : null);
|
||||||
if (usesTwoHand) {
|
if (usesTwoHand) {
|
||||||
Vector3f handballen = (palmOffsetLocal != null)
|
applyTwoHandTransform(weaponSpatial, attachInChar, boneRotInChar,
|
||||||
? boneInChar.subtract(boneRotInChar.mult(palmOffsetLocal.mult(0.5f)))
|
wGrip, charWorldRotInv, charWorldPos, cfg);
|
||||||
: boneInChar;
|
|
||||||
applyTwoHandTransform(weaponSpatial, handballen, boneRotInChar,
|
|
||||||
wGrip, charWorldRotInv, charWorldPos);
|
|
||||||
} else {
|
} else {
|
||||||
applyHandTransform(weaponSpatial, attachInChar, boneRotInChar, wGrip);
|
applyHandTransform(weaponSpatial, attachInChar, boneRotInChar, wGrip, cfg);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
boolean twoHanded = weaponItem != null
|
||||||
|
&& weaponItem.getSubCategory() == ItemSubCategory.TWO_HANDED_SWORD;
|
||||||
applyBackTransform(weaponSpatial, boneInChar, boneRotInChar,
|
applyBackTransform(weaponSpatial, boneInChar, boneRotInChar,
|
||||||
weaponItem != null ? weaponItem.getGrip() : null);
|
weaponItem != null ? weaponItem.getGrip() : null, twoHanded);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -168,7 +174,9 @@ public class NpcWeaponAttachmentControl extends AbstractControl {
|
|||||||
if (drawn && leftForeArmNode != null) {
|
if (drawn && leftForeArmNode != null) {
|
||||||
Vector3f boneInChar = charWorldRotInv.mult(leftForeArmNode.getWorldTranslation().subtract(charWorldPos));
|
Vector3f boneInChar = charWorldRotInv.mult(leftForeArmNode.getWorldTranslation().subtract(charWorldPos));
|
||||||
Quaternion boneRotInChar = charWorldRotInv.mult(leftForeArmNode.getWorldRotation());
|
Quaternion boneRotInChar = charWorldRotInv.mult(leftForeArmNode.getWorldRotation());
|
||||||
applyShieldArmTransform(shieldSpatial, boneInChar, boneRotInChar, grip);
|
ShieldCategoryAttachment shieldCfg = ShieldAttachmentIO.get(
|
||||||
|
shieldItem != null ? shieldItem.getSubCategory() : null);
|
||||||
|
applyShieldArmTransform(shieldSpatial, boneInChar, boneRotInChar, grip, shieldCfg);
|
||||||
shieldSpatial.setCullHint(Spatial.CullHint.Inherit);
|
shieldSpatial.setCullHint(Spatial.CullHint.Inherit);
|
||||||
} else if (!drawn && backNode != null) {
|
} else if (!drawn && backNode != null) {
|
||||||
Vector3f boneInChar = charWorldRotInv.mult(backNode.getWorldTranslation().subtract(charWorldPos));
|
Vector3f boneInChar = charWorldRotInv.mult(backNode.getWorldTranslation().subtract(charWorldPos));
|
||||||
@@ -196,7 +204,8 @@ public class NpcWeaponAttachmentControl extends AbstractControl {
|
|||||||
// ── Hand-Transform (1:1 von WeaponAttachmentControl) ─────────────────────────
|
// ── Hand-Transform (1:1 von WeaponAttachmentControl) ─────────────────────────
|
||||||
|
|
||||||
private void applyHandTransform(Spatial s, Vector3f bonePosInChar,
|
private void applyHandTransform(Spatial s, Vector3f bonePosInChar,
|
||||||
Quaternion boneRotInChar, ItemGrip grip) {
|
Quaternion boneRotInChar, ItemGrip grip,
|
||||||
|
WeaponCategoryAttachment cfg) {
|
||||||
Quaternion gripRot = (grip != null)
|
Quaternion gripRot = (grip != null)
|
||||||
? new Quaternion().fromAngles(DEG_TO_RAD * grip.getRotX(),
|
? new Quaternion().fromAngles(DEG_TO_RAD * grip.getRotX(),
|
||||||
DEG_TO_RAD * grip.getRotY(),
|
DEG_TO_RAD * grip.getRotY(),
|
||||||
@@ -208,22 +217,29 @@ public class NpcWeaponAttachmentControl extends AbstractControl {
|
|||||||
Quaternion animDelta = (handBindRotInChar != null)
|
Quaternion animDelta = (handBindRotInChar != null)
|
||||||
? boneRotInChar.mult(handBindRotInChar.inverse())
|
? boneRotInChar.mult(handBindRotInChar.inverse())
|
||||||
: boneRotInChar;
|
: boneRotInChar;
|
||||||
boolean bladeAtMinusZ = grip != null && grip.getGrip2PosZ() != 0f && grip.getGrip2PosZ() > grip.getPosZ();
|
boolean bladeAtMinusZ = grip != null && (
|
||||||
|
(grip.getGrip2PosX() != 0f || grip.getGrip2PosY() != 0f || grip.getGrip2PosZ() != 0f)
|
||||||
|
? grip.getGrip2PosZ() > grip.getPosZ()
|
||||||
|
: grip.getPosZ() > 0f);
|
||||||
Quaternion bladeFlip = bladeAtMinusZ
|
Quaternion bladeFlip = bladeAtMinusZ
|
||||||
? new Quaternion().fromAngles(com.jme3.math.FastMath.PI, 0f, 0f)
|
? new Quaternion().fromAngles(com.jme3.math.FastMath.PI, 0f, 0f)
|
||||||
: new Quaternion();
|
: new Quaternion();
|
||||||
Quaternion weaponRot = animDelta.mult(bladeFlip).mult(gripRot.inverse());
|
Quaternion weaponRot = animDelta.mult(bladeFlip).mult(gripRot.inverse());
|
||||||
Vector3f handOff = bladeAtMinusZ
|
Vector3f handOff = bladeAtMinusZ
|
||||||
? new Vector3f(HAND_GRIP_OFFSET.x, HAND_GRIP_OFFSET.y, -HAND_GRIP_OFFSET.z)
|
? new Vector3f(cfg.getHandOffX(), cfg.getHandOffY(), -cfg.getHandOffZ())
|
||||||
: HAND_GRIP_OFFSET;
|
: new Vector3f(cfg.getHandOffX(), cfg.getHandOffY(), cfg.getHandOffZ());
|
||||||
Vector3f weaponPos = bonePosInChar.subtract(weaponRot.mult(gripPos));
|
Vector3f weaponPos = bonePosInChar.subtract(weaponRot.mult(gripPos));
|
||||||
weaponPos = weaponPos.add(weaponRot.mult(handOff));
|
weaponPos = weaponPos.add(weaponRot.mult(handOff));
|
||||||
|
if (grip != null && (grip.getHandOffsetX() != 0f || grip.getHandOffsetY() != 0f || grip.getHandOffsetZ() != 0f)) {
|
||||||
|
weaponPos = weaponPos.add(grip.getHandOffsetX(), grip.getHandOffsetY(), grip.getHandOffsetZ());
|
||||||
|
}
|
||||||
s.setLocalRotation(weaponRot);
|
s.setLocalRotation(weaponRot);
|
||||||
s.setLocalTranslation(weaponPos);
|
s.setLocalTranslation(weaponPos);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void applyTwoHandTransform(Spatial s, Vector3f bonePosInChar, Quaternion boneRotInChar,
|
private void applyTwoHandTransform(Spatial s, Vector3f bonePosInChar, Quaternion boneRotInChar,
|
||||||
ItemGrip grip, Quaternion charWorldRotInv, Vector3f charWorldPos) {
|
ItemGrip grip, Quaternion charWorldRotInv, Vector3f charWorldPos,
|
||||||
|
WeaponCategoryAttachment cfg) {
|
||||||
Quaternion gripRot = new Quaternion().fromAngles(
|
Quaternion gripRot = new Quaternion().fromAngles(
|
||||||
DEG_TO_RAD * grip.getRotX(), DEG_TO_RAD * grip.getRotY(), DEG_TO_RAD * grip.getRotZ());
|
DEG_TO_RAD * grip.getRotX(), DEG_TO_RAD * grip.getRotY(), DEG_TO_RAD * grip.getRotZ());
|
||||||
Quaternion animDelta = (handBindRotInChar != null)
|
Quaternion animDelta = (handBindRotInChar != null)
|
||||||
@@ -236,28 +252,21 @@ public class NpcWeaponAttachmentControl extends AbstractControl {
|
|||||||
|
|
||||||
Vector3f g1 = new Vector3f(grip.getPosX(), grip.getPosY(), grip.getPosZ());
|
Vector3f g1 = new Vector3f(grip.getPosX(), grip.getPosY(), grip.getPosZ());
|
||||||
Vector3f g2 = new Vector3f(grip.getGrip2PosX(), grip.getGrip2PosY(), grip.getGrip2PosZ());
|
Vector3f g2 = new Vector3f(grip.getGrip2PosX(), grip.getGrip2PosY(), grip.getGrip2PosZ());
|
||||||
// Griffspanne virtuell auf 22cm; g1 (rechte Hand) 10cm weiter Richtung Klinge
|
Vector3f handOff = bladeAtMinusZ
|
||||||
Vector3f gripAxis = g1.subtract(g2).normalizeLocal();
|
? new Vector3f(cfg.getHandOffX(), cfg.getHandOffY(), -cfg.getHandOffZ())
|
||||||
Vector3f gripCenter = g1.add(g2).multLocal(0.5f);
|
: new Vector3f(cfg.getHandOffX(), cfg.getHandOffY(), cfg.getHandOffZ());
|
||||||
g1 = gripCenter.add(gripAxis.mult(0.21f));
|
Vector3f hand2Off = bladeAtMinusZ
|
||||||
g2 = gripCenter.subtract(gripAxis.mult(0.11f));
|
? new Vector3f(cfg.getHand2OffX(), cfg.getHand2OffY(), -cfg.getHand2OffZ())
|
||||||
|
: new Vector3f(cfg.getHand2OffX(), cfg.getHand2OffY(), cfg.getHand2OffZ());
|
||||||
|
Vector3f g1eff = g1.subtract(handOff);
|
||||||
|
Vector3f g2eff = g2.add(hand2Off);
|
||||||
|
|
||||||
Vector3f leftHandInChar = charWorldRotInv.mult(leftForeArmNode.getWorldTranslation().subtract(charWorldPos));
|
Vector3f leftHandInChar = charWorldRotInv.mult(leftForeArmNode.getWorldTranslation().subtract(charWorldPos));
|
||||||
Quaternion leftBoneRotInChar = charWorldRotInv.mult(leftForeArmNode.getWorldRotation());
|
Vector3f currentGripDir = weaponRot.mult(g2eff.subtract(g1eff).normalizeLocal());
|
||||||
Vector3f leftHandballen = (palmOffsetLocal != null)
|
Vector3f targetDir = leftHandInChar.subtract(bonePosInChar).normalizeLocal();
|
||||||
? leftHandInChar.subtract(leftBoneRotInChar.mult(palmOffsetLocal.mult(0.5f)))
|
|
||||||
: leftHandInChar;
|
|
||||||
leftHandballen.addLocal(0.10f, 0f, 0f);
|
|
||||||
|
|
||||||
// Swing: Griffachse auf linken Handballen ausrichten (inkl. X-Korrektur)
|
|
||||||
Vector3f currentGripDir = weaponRot.mult(g2.subtract(g1).normalizeLocal());
|
|
||||||
Vector3f targetDir = leftHandballen.subtract(bonePosInChar).normalizeLocal();
|
|
||||||
float dot = currentGripDir.dot(targetDir);
|
|
||||||
if (dot > 0f) {
|
|
||||||
weaponRot = swingFromTo(currentGripDir, targetDir).mult(weaponRot);
|
weaponRot = swingFromTo(currentGripDir, targetDir).mult(weaponRot);
|
||||||
}
|
|
||||||
|
|
||||||
// g1 exakt an der rechten Hand anpinnen — linke-Hand-Korrekturen beeinflussen rechte nicht
|
Vector3f weaponPos = bonePosInChar.subtract(weaponRot.mult(g1eff));
|
||||||
Vector3f weaponPos = bonePosInChar.subtract(weaponRot.mult(g1));
|
|
||||||
s.setLocalRotation(weaponRot);
|
s.setLocalRotation(weaponRot);
|
||||||
s.setLocalTranslation(weaponPos);
|
s.setLocalTranslation(weaponPos);
|
||||||
}
|
}
|
||||||
@@ -279,22 +288,26 @@ public class NpcWeaponAttachmentControl extends AbstractControl {
|
|||||||
private static final Quaternion BACK_ROT_NORM = new Quaternion(0f, -(float) Math.sqrt(0.5), (float) Math.sqrt(0.5), 0f);
|
private static final Quaternion BACK_ROT_NORM = new Quaternion(0f, -(float) Math.sqrt(0.5), (float) Math.sqrt(0.5), 0f);
|
||||||
|
|
||||||
private void applyBackTransform(Spatial s, Vector3f boneInChar,
|
private void applyBackTransform(Spatial s, Vector3f boneInChar,
|
||||||
Quaternion boneRotInChar, ItemGrip grip) {
|
Quaternion boneRotInChar, ItemGrip grip, boolean twoHanded) {
|
||||||
Quaternion gripRot = (grip != null)
|
Quaternion gripRot = (grip != null)
|
||||||
? new Quaternion().fromAngles(DEG_TO_RAD * grip.getRotX(),
|
? new Quaternion().fromAngles(DEG_TO_RAD * grip.getRotX(),
|
||||||
DEG_TO_RAD * grip.getRotY(),
|
DEG_TO_RAD * grip.getRotY(),
|
||||||
DEG_TO_RAD * grip.getRotZ())
|
DEG_TO_RAD * grip.getRotZ())
|
||||||
: new Quaternion();
|
: new Quaternion();
|
||||||
boolean bladeAtMinusZ2 = grip != null && grip.getGrip2PosZ() != 0f && grip.getGrip2PosZ() > grip.getPosZ();
|
boolean bladeAtMinusZ2 = grip != null && (
|
||||||
|
(grip.getGrip2PosX() != 0f || grip.getGrip2PosY() != 0f || grip.getGrip2PosZ() != 0f)
|
||||||
|
? grip.getGrip2PosZ() > grip.getPosZ()
|
||||||
|
: grip.getPosZ() > 0f);
|
||||||
Quaternion bladeFlip2 = bladeAtMinusZ2
|
Quaternion bladeFlip2 = bladeAtMinusZ2
|
||||||
? new Quaternion().fromAngles(com.jme3.math.FastMath.PI, 0f, 0f)
|
? new Quaternion().fromAngles(com.jme3.math.FastMath.PI, 0f, 0f)
|
||||||
: new Quaternion();
|
: new Quaternion();
|
||||||
Quaternion weaponRot = boneRotInChar.mult(BACK_ROT_NORM).mult(bladeFlip2).mult(gripRot.inverse());
|
Quaternion weaponRot = boneRotInChar.mult(BACK_ROT_NORM).mult(bladeFlip2).mult(gripRot.inverse());
|
||||||
Vector3f target = boneInChar.add(boneRotInChar.mult(BACK_GRIP_OFFSET));
|
Vector3f target = boneInChar.add(boneRotInChar.mult(
|
||||||
|
twoHanded ? TWO_HAND_BACK_GRIP_OFFSET : BACK_GRIP_OFFSET));
|
||||||
|
// X=0: Waffenmitte (nicht handspezifischer Griffpunkt) auf Wirbelsäule zentrieren
|
||||||
Vector3f gripPos = grip != null
|
Vector3f gripPos = grip != null
|
||||||
? new Vector3f(grip.getPosX(), grip.getPosY(), grip.getPosZ()) : Vector3f.ZERO;
|
? new Vector3f(0f, grip.getPosY(), grip.getPosZ()) : Vector3f.ZERO;
|
||||||
Vector3f backPos = target.subtract(weaponRot.mult(gripPos));
|
Vector3f backPos = target.subtract(weaponRot.mult(gripPos));
|
||||||
if (bladeAtMinusZ2) backPos = backPos.add(0.1f, 0f, 0f);
|
|
||||||
s.setLocalRotation(weaponRot);
|
s.setLocalRotation(weaponRot);
|
||||||
s.setLocalTranslation(backPos);
|
s.setLocalTranslation(backPos);
|
||||||
}
|
}
|
||||||
@@ -302,15 +315,17 @@ public class NpcWeaponAttachmentControl extends AbstractControl {
|
|||||||
// ── Schild-Transforms (1:1 von WeaponAttachmentControl) ──────────────────────
|
// ── Schild-Transforms (1:1 von WeaponAttachmentControl) ──────────────────────
|
||||||
|
|
||||||
private void applyShieldArmTransform(Spatial s, Vector3f bonePosInChar,
|
private void applyShieldArmTransform(Spatial s, Vector3f bonePosInChar,
|
||||||
Quaternion boneRotInChar, ItemGrip grip) {
|
Quaternion boneRotInChar, ItemGrip grip,
|
||||||
|
ShieldCategoryAttachment cfg) {
|
||||||
Quaternion animDelta = (leftForeArmBindRotInChar != null)
|
Quaternion animDelta = (leftForeArmBindRotInChar != null)
|
||||||
? boneRotInChar.mult(leftForeArmBindRotInChar.inverse())
|
? boneRotInChar.mult(leftForeArmBindRotInChar.inverse())
|
||||||
: boneRotInChar;
|
: boneRotInChar;
|
||||||
Quaternion armRot = new Quaternion().fromAngles(SHIELD_ARM_ROT_X, SHIELD_ARM_ROT_Y, SHIELD_ARM_ROT_Z);
|
Quaternion armRot = new Quaternion().fromAngles(
|
||||||
|
DEG_TO_RAD * cfg.getRotX(), DEG_TO_RAD * cfg.getRotY(), DEG_TO_RAD * cfg.getRotZ());
|
||||||
Quaternion shieldRot = animDelta.mult(armRot);
|
Quaternion shieldRot = animDelta.mult(armRot);
|
||||||
Vector3f gripPos = (grip != null)
|
Vector3f gripPos = (grip != null)
|
||||||
? new Vector3f(grip.getPosX(), grip.getPosY(), grip.getPosZ()) : Vector3f.ZERO;
|
? new Vector3f(grip.getPosX(), grip.getPosY(), grip.getPosZ()) : Vector3f.ZERO;
|
||||||
Vector3f anchor = bonePosInChar.add(boneRotInChar.mult(new Vector3f(0f, -SHIELD_ARM_ELBOW_OFFSET, 0f)));
|
Vector3f anchor = bonePosInChar.add(boneRotInChar.mult(new Vector3f(0f, -cfg.getElbowOffset(), 0f)));
|
||||||
s.setLocalRotation(shieldRot);
|
s.setLocalRotation(shieldRot);
|
||||||
s.setLocalTranslation(anchor.subtract(shieldRot.mult(gripPos)));
|
s.setLocalTranslation(anchor.subtract(shieldRot.mult(gripPos)));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import com.jme3.anim.tween.Tween;
|
|||||||
import com.jme3.anim.tween.action.BaseAction;
|
import com.jme3.anim.tween.action.BaseAction;
|
||||||
import com.jme3.bullet.control.CharacterControl;
|
import com.jme3.bullet.control.CharacterControl;
|
||||||
import com.jme3.input.InputManager;
|
import com.jme3.input.InputManager;
|
||||||
|
import com.jme3.input.KeyInput;
|
||||||
import com.jme3.input.MouseInput;
|
import com.jme3.input.MouseInput;
|
||||||
import com.jme3.input.RawInputListener;
|
import com.jme3.input.RawInputListener;
|
||||||
import com.jme3.input.controls.ActionListener;
|
import com.jme3.input.controls.ActionListener;
|
||||||
@@ -110,11 +111,14 @@ public class PlayerInputControl implements CombatStyleBase.AnimReceiver {
|
|||||||
private static final String ACT_COMBAT_ALT = "CombatAltAction";
|
private static final String ACT_COMBAT_ALT = "CombatAltAction";
|
||||||
private static final String ACT_ROLL = "Roll";
|
private static final String ACT_ROLL = "Roll";
|
||||||
private static final String ACT_SNEAK = "Sneak";
|
private static final String ACT_SNEAK = "Sneak";
|
||||||
|
private static final String ACT_BORED_1 = "BoredAnim1";
|
||||||
|
private static final String ACT_BORED_2 = "BoredAnim2";
|
||||||
|
private static final String ACT_BORED_3 = "BoredAnim3";
|
||||||
|
|
||||||
private static final String[] ACTION_NAMES = {
|
private static final String[] ACTION_NAMES = {
|
||||||
"Forward", "Backward", "Left", "Right", "Jump", "Sprint", "Walk",
|
"Forward", "Backward", "Left", "Right", "Jump", "Sprint", "Walk",
|
||||||
ACT_DRAW_MELEE, ACT_DRAW_RANGED, ACT_SHEATH, ACT_COMBAT_ATK, ACT_COMBAT_ALT,
|
ACT_DRAW_MELEE, ACT_DRAW_RANGED, ACT_SHEATH, ACT_COMBAT_ATK, ACT_COMBAT_ALT,
|
||||||
ACT_ROLL, ACT_SNEAK
|
ACT_ROLL, ACT_SNEAK, ACT_BORED_1, ACT_BORED_2, ACT_BORED_3
|
||||||
};
|
};
|
||||||
|
|
||||||
private final InputManager inputManager;
|
private final InputManager inputManager;
|
||||||
@@ -383,6 +387,9 @@ public class PlayerInputControl implements CombatStyleBase.AnimReceiver {
|
|||||||
case ACT_COMBAT_ALT -> handleCombatAlt(isPressed);
|
case ACT_COMBAT_ALT -> handleCombatAlt(isPressed);
|
||||||
case ACT_ROLL -> { if (isPressed) triggerRollFromInput(); }
|
case ACT_ROLL -> { if (isPressed) triggerRollFromInput(); }
|
||||||
case ACT_SNEAK -> { if (!weaponDrawn) handleSneakKey(isPressed); }
|
case ACT_SNEAK -> { if (!weaponDrawn) handleSneakKey(isPressed); }
|
||||||
|
case ACT_BORED_1 -> { if (isPressed) triggerBoredAnim(0); }
|
||||||
|
case ACT_BORED_2 -> { if (isPressed) triggerBoredAnim(1); }
|
||||||
|
case ACT_BORED_3 -> { if (isPressed) triggerBoredAnim(2); }
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -400,6 +407,8 @@ public class PlayerInputControl implements CombatStyleBase.AnimReceiver {
|
|||||||
this.visual = visual;
|
this.visual = visual;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Spatial getVisual() { return visual; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Setzt die Navigationsquellen (PathFinder + Terrain). Muss vor oder beim
|
* Setzt die Navigationsquellen (PathFinder + Terrain). Muss vor oder beim
|
||||||
* ersten setAnimationContext gesetzt sein, damit der CharacterNavigator
|
* ersten setAnimationContext gesetzt sein, damit der CharacterNavigator
|
||||||
@@ -468,6 +477,7 @@ public class PlayerInputControl implements CombatStyleBase.AnimReceiver {
|
|||||||
|
|
||||||
public void setMainCharacter(MainCharacter mc) { this.mainCharacter = mc; }
|
public void setMainCharacter(MainCharacter mc) { this.mainCharacter = mc; }
|
||||||
public boolean isWeaponDrawn() { return weaponDrawn; }
|
public boolean isWeaponDrawn() { return weaponDrawn; }
|
||||||
|
public boolean isDrawAnimActive() { return drawAnimActive; }
|
||||||
public ItemSubCategory getDrawnWeaponType() { return drawnWeaponType; }
|
public ItemSubCategory getDrawnWeaponType() { return drawnWeaponType; }
|
||||||
public boolean isLastAttackCritical() { return lastAttackCritical; }
|
public boolean isLastAttackCritical() { return lastAttackCritical; }
|
||||||
public com.jme3.anim.SkinningControl getSkinningControl() { return skinningControl; }
|
public com.jme3.anim.SkinningControl getSkinningControl() { return skinningControl; }
|
||||||
@@ -832,6 +842,9 @@ public class PlayerInputControl implements CombatStyleBase.AnimReceiver {
|
|||||||
inputManager.addMapping(ACT_COMBAT_ALT, new MouseButtonTrigger(MouseInput.BUTTON_RIGHT));
|
inputManager.addMapping(ACT_COMBAT_ALT, new MouseButtonTrigger(MouseInput.BUTTON_RIGHT));
|
||||||
inputManager.addMapping(ACT_ROLL, new KeyTrigger(kb.roll));
|
inputManager.addMapping(ACT_ROLL, new KeyTrigger(kb.roll));
|
||||||
inputManager.addMapping(ACT_SNEAK, new KeyTrigger(kb.sneak));
|
inputManager.addMapping(ACT_SNEAK, new KeyTrigger(kb.sneak));
|
||||||
|
inputManager.addMapping(ACT_BORED_1, new KeyTrigger(KeyInput.KEY_F1));
|
||||||
|
inputManager.addMapping(ACT_BORED_2, new KeyTrigger(KeyInput.KEY_F2));
|
||||||
|
inputManager.addMapping(ACT_BORED_3, new KeyTrigger(KeyInput.KEY_F3));
|
||||||
inputManager.addListener(actionListener, ACTION_NAMES);
|
inputManager.addListener(actionListener, ACTION_NAMES);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1222,7 +1235,8 @@ public class PlayerInputControl implements CombatStyleBase.AnimReceiver {
|
|||||||
} else {
|
} else {
|
||||||
boolean swordShield = drawnWeaponType == de.blight.common.model.ItemSubCategory.SWORD
|
boolean swordShield = drawnWeaponType == de.blight.common.model.ItemSubCategory.SWORD
|
||||||
&& hasShieldEquipped();
|
&& hasShieldEquipped();
|
||||||
if (swordShield) {
|
boolean twoHand = drawnWeaponType == ItemSubCategory.TWO_HANDED_SWORD;
|
||||||
|
if (swordShield || twoHand) {
|
||||||
if (boredAnimRemaining > 0f) {
|
if (boredAnimRemaining > 0f) {
|
||||||
boredAnimRemaining -= tpf;
|
boredAnimRemaining -= tpf;
|
||||||
target = boredVariant;
|
target = boredVariant;
|
||||||
@@ -1231,9 +1245,15 @@ public class PlayerInputControl implements CombatStyleBase.AnimReceiver {
|
|||||||
if (boredIdleTimer >= BORED_IDLE_DELAY) {
|
if (boredIdleTimer >= BORED_IDLE_DELAY) {
|
||||||
boredIdleTimer = 0f;
|
boredIdleTimer = 0f;
|
||||||
int pick = playerRng.nextInt(3);
|
int pick = playerRng.nextInt(3);
|
||||||
|
if (swordShield) {
|
||||||
boredVariant = pick == 0 ? AnimationAction.SWORD_IDLE_BORED
|
boredVariant = pick == 0 ? AnimationAction.SWORD_IDLE_BORED
|
||||||
: pick == 1 ? AnimationAction.SWORD_IDLE_BORED_2
|
: pick == 1 ? AnimationAction.SWORD_IDLE_BORED_2
|
||||||
: AnimationAction.SWORD_IDLE_BORED_3;
|
: AnimationAction.SWORD_IDLE_BORED_3;
|
||||||
|
} else {
|
||||||
|
boredVariant = pick == 0 ? AnimationAction.TWO_HAND_IDLE_BORED
|
||||||
|
: pick == 1 ? AnimationAction.TWO_HAND_IDLE_BORED_2
|
||||||
|
: AnimationAction.TWO_HAND_IDLE_BORED_3;
|
||||||
|
}
|
||||||
boredAnimRemaining = resolveClipLength(boredVariant, 3.0f);
|
boredAnimRemaining = resolveClipLength(boredVariant, 3.0f);
|
||||||
target = boredVariant;
|
target = boredVariant;
|
||||||
} else {
|
} else {
|
||||||
@@ -1409,6 +1429,12 @@ public class PlayerInputControl implements CombatStyleBase.AnimReceiver {
|
|||||||
private void handleCombatAlt(boolean isPressed) {
|
private void handleCombatAlt(boolean isPressed) {
|
||||||
if (!weaponDrawn) return;
|
if (!weaponDrawn) return;
|
||||||
if (isMeleeType(drawnWeaponType)) {
|
if (isMeleeType(drawnWeaponType)) {
|
||||||
|
// Schwert blockt per Schild – ohne Schild ist Blocken nicht möglich.
|
||||||
|
// Zweihänder/Axt blocken mit der Waffe selbst, brauchen keinen Schild.
|
||||||
|
if (drawnWeaponType == ItemSubCategory.SWORD && !hasShieldEquipped()) {
|
||||||
|
rightMouseHeld = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (isPressed) {
|
if (isPressed) {
|
||||||
rightMouseHeld = true;
|
rightMouseHeld = true;
|
||||||
// Sofort blocken nur wenn keine andere Animation läuft
|
// Sofort blocken nur wenn keine andere Animation läuft
|
||||||
@@ -1676,6 +1702,25 @@ public class PlayerInputControl implements CombatStyleBase.AnimReceiver {
|
|||||||
currentAnim = AnimationAction.CROUCH_TO_STAND;
|
currentAnim = AnimationAction.CROUCH_TO_STAND;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void triggerBoredAnim(int variant) {
|
||||||
|
if (!weaponDrawn || forward || backward || left || right || combatAnimActive || drawAnimActive) return;
|
||||||
|
if (physicsChar != null && !physicsChar.onGround()) return;
|
||||||
|
boolean swordShield = drawnWeaponType == de.blight.common.model.ItemSubCategory.SWORD && hasShieldEquipped();
|
||||||
|
boolean twoHand = drawnWeaponType == ItemSubCategory.TWO_HANDED_SWORD;
|
||||||
|
if (!swordShield && !twoHand) return;
|
||||||
|
if (swordShield) {
|
||||||
|
boredVariant = variant == 0 ? AnimationAction.SWORD_IDLE_BORED
|
||||||
|
: variant == 1 ? AnimationAction.SWORD_IDLE_BORED_2
|
||||||
|
: AnimationAction.SWORD_IDLE_BORED_3;
|
||||||
|
} else {
|
||||||
|
boredVariant = variant == 0 ? AnimationAction.TWO_HAND_IDLE_BORED
|
||||||
|
: variant == 1 ? AnimationAction.TWO_HAND_IDLE_BORED_2
|
||||||
|
: AnimationAction.TWO_HAND_IDLE_BORED_3;
|
||||||
|
}
|
||||||
|
boredAnimRemaining = resolveClipLength(boredVariant, 3.0f);
|
||||||
|
boredIdleTimer = 0f;
|
||||||
|
}
|
||||||
|
|
||||||
private void triggerRollFromInput() {
|
private void triggerRollFromInput() {
|
||||||
if (!weaponDrawn) return;
|
if (!weaponDrawn) return;
|
||||||
if (rollActive || pickupActive || blockingAnimActive || turnActive
|
if (rollActive || pickupActive || blockingAnimActive || turnActive
|
||||||
|
|||||||
@@ -13,6 +13,10 @@ import de.blight.common.model.Item;
|
|||||||
import de.blight.common.model.ItemGrip;
|
import de.blight.common.model.ItemGrip;
|
||||||
import de.blight.common.model.ItemSubCategory;
|
import de.blight.common.model.ItemSubCategory;
|
||||||
import de.blight.common.model.MainCharacter;
|
import de.blight.common.model.MainCharacter;
|
||||||
|
import de.blight.common.model.ShieldAttachmentIO;
|
||||||
|
import de.blight.common.model.ShieldCategoryAttachment;
|
||||||
|
import de.blight.common.model.WeaponAttachmentIO;
|
||||||
|
import de.blight.common.model.WeaponCategoryAttachment;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -37,6 +41,7 @@ public class WeaponAttachmentControl extends AbstractControl {
|
|||||||
private static final float HALF_PI = (float) Math.PI * 0.5f;
|
private static final float HALF_PI = (float) Math.PI * 0.5f;
|
||||||
|
|
||||||
private static final Vector3f BACK_GRIP_OFFSET = new Vector3f(0.0f, 0.40f, -0.13f);
|
private static final Vector3f BACK_GRIP_OFFSET = new Vector3f(0.0f, 0.40f, -0.13f);
|
||||||
|
private static final Vector3f TWO_HAND_BACK_GRIP_OFFSET = new Vector3f(0.0f, 0.40f, -0.03f);
|
||||||
private static final Vector3f HAND_GRIP_OFFSET = new Vector3f(
|
private static final Vector3f HAND_GRIP_OFFSET = new Vector3f(
|
||||||
ItemGrip.HAND_GRIP_OFFSET_X, ItemGrip.HAND_GRIP_OFFSET_Y, ItemGrip.HAND_GRIP_OFFSET_Z);
|
ItemGrip.HAND_GRIP_OFFSET_X, ItemGrip.HAND_GRIP_OFFSET_Y, ItemGrip.HAND_GRIP_OFFSET_Z);
|
||||||
|
|
||||||
@@ -67,6 +72,7 @@ public class WeaponAttachmentControl extends AbstractControl {
|
|||||||
// Wird im 2. Frame aus den Runtime-Node-Positionen berechnet (skalierungs-unabhängig).
|
// Wird im 2. Frame aus den Runtime-Node-Positionen berechnet (skalierungs-unabhängig).
|
||||||
private Vector3f palmOffsetLocal;
|
private Vector3f palmOffsetLocal;
|
||||||
|
|
||||||
|
|
||||||
private Item lastEquippedWeapon;
|
private Item lastEquippedWeapon;
|
||||||
private boolean lastDrawnState = false;
|
private boolean lastDrawnState = false;
|
||||||
private Spatial weaponSpatial;
|
private Spatial weaponSpatial;
|
||||||
@@ -188,27 +194,27 @@ public class WeaponAttachmentControl extends AbstractControl {
|
|||||||
|
|
||||||
if (drawn) {
|
if (drawn) {
|
||||||
boolean isTwoHand = item != null && item.getSubCategory() == ItemSubCategory.TWO_HANDED_SWORD;
|
boolean isTwoHand = item != null && item.getSubCategory() == ItemSubCategory.TWO_HANDED_SWORD;
|
||||||
|
WeaponCategoryAttachment cfg = WeaponAttachmentIO.get(item != null ? item.getSubCategory() : null);
|
||||||
Vector3f attachInChar = (palmOffsetLocal != null)
|
Vector3f attachInChar = (palmOffsetLocal != null)
|
||||||
? boneInChar.add(boneRotInChar.mult(palmOffsetLocal.mult(0.5f)))
|
? boneInChar.add(boneRotInChar.mult(palmOffsetLocal.mult(0.5f)))
|
||||||
: boneInChar;
|
: boneInChar;
|
||||||
boolean usesTwoHand = isTwoHand && leftForeArmNode != null
|
boolean usesTwoHand = isTwoHand && leftForeArmNode != null
|
||||||
&& grip != null
|
&& grip != null
|
||||||
&& (grip.getGrip2PosX() != 0f || grip.getGrip2PosY() != 0f || grip.getGrip2PosZ() != 0f);
|
&& (grip.getGrip2PosX() != 0f || grip.getGrip2PosY() != 0f || grip.getGrip2PosZ() != 0f)
|
||||||
|
&& !playerInput.isDrawAnimActive();
|
||||||
if (usesTwoHand) {
|
if (usesTwoHand) {
|
||||||
// Handballen: entgegengesetzte Richtung von palmOffset (der Richtung Finger zeigt)
|
applyTwoHandTransform(attachInChar, boneRotInChar, grip, charWorldRotInv, charWorldPos, cfg);
|
||||||
Vector3f handballen = (palmOffsetLocal != null)
|
|
||||||
? boneInChar.subtract(boneRotInChar.mult(palmOffsetLocal.mult(0.5f)))
|
|
||||||
: boneInChar;
|
|
||||||
applyTwoHandTransform(handballen, boneRotInChar, grip, charWorldRotInv, charWorldPos);
|
|
||||||
} else {
|
} else {
|
||||||
applyHandTransform(attachInChar, boneRotInChar, grip);
|
applyHandTransform(attachInChar, boneRotInChar, grip, cfg);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
applyBackTransform(boneInChar, boneRotInChar, grip);
|
boolean twoHanded = item != null && item.getSubCategory() == ItemSubCategory.TWO_HANDED_SWORD;
|
||||||
|
applyBackTransform(boneInChar, boneRotInChar, grip, twoHanded);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void applyHandTransform(Vector3f bonePosInChar, Quaternion boneRotInChar, ItemGrip grip) {
|
private void applyHandTransform(Vector3f bonePosInChar, Quaternion boneRotInChar, ItemGrip grip,
|
||||||
|
WeaponCategoryAttachment cfg) {
|
||||||
Quaternion gripRot = (grip != null)
|
Quaternion gripRot = (grip != null)
|
||||||
? new Quaternion().fromAngles(DEG_TO_RAD * grip.getRotX(), DEG_TO_RAD * grip.getRotY(), DEG_TO_RAD * grip.getRotZ())
|
? new Quaternion().fromAngles(DEG_TO_RAD * grip.getRotX(), DEG_TO_RAD * grip.getRotY(), DEG_TO_RAD * grip.getRotZ())
|
||||||
: new Quaternion();
|
: new Quaternion();
|
||||||
@@ -219,7 +225,10 @@ public class WeaponAttachmentControl extends AbstractControl {
|
|||||||
Quaternion animDelta = (handBindRotInChar != null)
|
Quaternion animDelta = (handBindRotInChar != null)
|
||||||
? boneRotInChar.mult(handBindRotInChar.inverse())
|
? boneRotInChar.mult(handBindRotInChar.inverse())
|
||||||
: boneRotInChar;
|
: boneRotInChar;
|
||||||
boolean bladeAtMinusZ2 = grip != null && grip.getGrip2PosZ() != 0f && grip.getGrip2PosZ() > grip.getPosZ();
|
boolean bladeAtMinusZ2 = grip != null && (
|
||||||
|
(grip.getGrip2PosX() != 0f || grip.getGrip2PosY() != 0f || grip.getGrip2PosZ() != 0f)
|
||||||
|
? grip.getGrip2PosZ() > grip.getPosZ()
|
||||||
|
: grip.getPosZ() > 0f);
|
||||||
Quaternion bladeFlip2 = bladeAtMinusZ2
|
Quaternion bladeFlip2 = bladeAtMinusZ2
|
||||||
? new Quaternion().fromAngles(com.jme3.math.FastMath.PI, 0f, 0f)
|
? new Quaternion().fromAngles(com.jme3.math.FastMath.PI, 0f, 0f)
|
||||||
: new Quaternion();
|
: new Quaternion();
|
||||||
@@ -259,8 +268,8 @@ public class WeaponAttachmentControl extends AbstractControl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Vector3f handOff2 = bladeAtMinusZ2
|
Vector3f handOff2 = bladeAtMinusZ2
|
||||||
? new Vector3f(HAND_GRIP_OFFSET.x, HAND_GRIP_OFFSET.y, -HAND_GRIP_OFFSET.z)
|
? new Vector3f(cfg.getHandOffX(), cfg.getHandOffY(), -cfg.getHandOffZ())
|
||||||
: HAND_GRIP_OFFSET;
|
: new Vector3f(cfg.getHandOffX(), cfg.getHandOffY(), cfg.getHandOffZ());
|
||||||
Vector3f weaponPos = bonePosInChar.subtract(weaponRot.mult(gripPos));
|
Vector3f weaponPos = bonePosInChar.subtract(weaponRot.mult(gripPos));
|
||||||
weaponPos = weaponPos.add(weaponRot.mult(handOff2));
|
weaponPos = weaponPos.add(weaponRot.mult(handOff2));
|
||||||
if (grip != null && (grip.getHandOffsetX() != 0f || grip.getHandOffsetY() != 0f || grip.getHandOffsetZ() != 0f)) {
|
if (grip != null && (grip.getHandOffsetX() != 0f || grip.getHandOffsetY() != 0f || grip.getHandOffsetZ() != 0f)) {
|
||||||
@@ -272,7 +281,8 @@ public class WeaponAttachmentControl extends AbstractControl {
|
|||||||
|
|
||||||
private void applyTwoHandTransform(Vector3f bonePosInChar, Quaternion boneRotInChar,
|
private void applyTwoHandTransform(Vector3f bonePosInChar, Quaternion boneRotInChar,
|
||||||
ItemGrip grip,
|
ItemGrip grip,
|
||||||
Quaternion charWorldRotInv, Vector3f charWorldPos) {
|
Quaternion charWorldRotInv, Vector3f charWorldPos,
|
||||||
|
WeaponCategoryAttachment cfg) {
|
||||||
// Basis-Rotation aus rechter Hand (gibt Roll/Handflächenausrichtung)
|
// Basis-Rotation aus rechter Hand (gibt Roll/Handflächenausrichtung)
|
||||||
Quaternion gripRot = new Quaternion().fromAngles(
|
Quaternion gripRot = new Quaternion().fromAngles(
|
||||||
DEG_TO_RAD * grip.getRotX(), DEG_TO_RAD * grip.getRotY(), DEG_TO_RAD * grip.getRotZ());
|
DEG_TO_RAD * grip.getRotX(), DEG_TO_RAD * grip.getRotY(), DEG_TO_RAD * grip.getRotZ());
|
||||||
@@ -286,29 +296,25 @@ public class WeaponAttachmentControl extends AbstractControl {
|
|||||||
|
|
||||||
Vector3f g1 = new Vector3f(grip.getPosX(), grip.getPosY(), grip.getPosZ());
|
Vector3f g1 = new Vector3f(grip.getPosX(), grip.getPosY(), grip.getPosZ());
|
||||||
Vector3f g2 = new Vector3f(grip.getGrip2PosX(), grip.getGrip2PosY(), grip.getGrip2PosZ());
|
Vector3f g2 = new Vector3f(grip.getGrip2PosX(), grip.getGrip2PosY(), grip.getGrip2PosZ());
|
||||||
// Griffspanne virtuell auf 22cm; g1 (rechte Hand) 10cm weiter Richtung Klinge
|
// g1 hat handOff eingebaut (Kategorie-Config), g2 nicht → g1eff bereinigt den Unterschied
|
||||||
Vector3f gripAxis = g1.subtract(g2).normalizeLocal();
|
Vector3f handOff = bladeAtMinusZ
|
||||||
Vector3f gripCenter = g1.add(g2).multLocal(0.5f);
|
? new Vector3f(cfg.getHandOffX(), cfg.getHandOffY(), -cfg.getHandOffZ())
|
||||||
g1 = gripCenter.add(gripAxis.mult(0.21f));
|
: new Vector3f(cfg.getHandOffX(), cfg.getHandOffY(), cfg.getHandOffZ());
|
||||||
g2 = gripCenter.subtract(gripAxis.mult(0.11f));
|
// Korrektur g2 durch hand2Offset der Kategorie
|
||||||
|
Vector3f hand2Off = bladeAtMinusZ
|
||||||
|
? new Vector3f(cfg.getHand2OffX(), cfg.getHand2OffY(), -cfg.getHand2OffZ())
|
||||||
|
: new Vector3f(cfg.getHand2OffX(), cfg.getHand2OffY(), cfg.getHand2OffZ());
|
||||||
|
Vector3f g1eff = g1.subtract(handOff);
|
||||||
|
Vector3f g2eff = g2.add(hand2Off);
|
||||||
|
|
||||||
|
// Swing: g1eff→g2eff-Richtung auf rechteHand→linkeHand ausrichten
|
||||||
Vector3f leftHandInChar = charWorldRotInv.mult(leftForeArmNode.getWorldTranslation().subtract(charWorldPos));
|
Vector3f leftHandInChar = charWorldRotInv.mult(leftForeArmNode.getWorldTranslation().subtract(charWorldPos));
|
||||||
// Linke Hand: Handballen-Korrektur (Palm-Offset entgegen Fingerrichtung) – nur für midTarget, nicht für targetDir
|
Vector3f currentGripDir = weaponRot.mult(g2eff.subtract(g1eff).normalizeLocal());
|
||||||
Quaternion leftBoneRotInChar = charWorldRotInv.mult(leftForeArmNode.getWorldRotation());
|
Vector3f targetDir = leftHandInChar.subtract(bonePosInChar).normalizeLocal();
|
||||||
Vector3f leftHandballen = (palmOffsetLocal != null)
|
|
||||||
? leftHandInChar.subtract(leftBoneRotInChar.mult(palmOffsetLocal.mult(0.5f)))
|
|
||||||
: leftHandInChar;
|
|
||||||
leftHandballen.addLocal(0.10f, 0f, 0f);
|
|
||||||
|
|
||||||
// Swing: Griffachse auf linken Handballen ausrichten (inkl. X-Korrektur)
|
|
||||||
Vector3f currentGripDir = weaponRot.mult(g2.subtract(g1).normalizeLocal());
|
|
||||||
Vector3f targetDir = leftHandballen.subtract(bonePosInChar).normalizeLocal();
|
|
||||||
float dot = currentGripDir.dot(targetDir);
|
|
||||||
if (dot > 0f) {
|
|
||||||
weaponRot = swingFromTo(currentGripDir, targetDir).mult(weaponRot);
|
weaponRot = swingFromTo(currentGripDir, targetDir).mult(weaponRot);
|
||||||
}
|
|
||||||
|
|
||||||
// g1 exakt an der rechten Hand anpinnen — linke-Hand-Korrekturen beeinflussen rechte nicht
|
// g1eff an bonePosInChar pinnen
|
||||||
Vector3f weaponPos = bonePosInChar.subtract(weaponRot.mult(g1));
|
Vector3f weaponPos = bonePosInChar.subtract(weaponRot.mult(g1eff));
|
||||||
weaponSpatial.setLocalRotation(weaponRot);
|
weaponSpatial.setLocalRotation(weaponRot);
|
||||||
weaponSpatial.setLocalTranslation(weaponPos);
|
weaponSpatial.setLocalTranslation(weaponPos);
|
||||||
}
|
}
|
||||||
@@ -359,25 +365,29 @@ public class WeaponAttachmentControl extends AbstractControl {
|
|||||||
// Ergebnis für Kurzschwert (gripRot=fromAngles(0,-90°,90°)) identisch zur alten Formel.
|
// Ergebnis für Kurzschwert (gripRot=fromAngles(0,-90°,90°)) identisch zur alten Formel.
|
||||||
private static final Quaternion BACK_ROT_NORM = new Quaternion(0f, -(float) Math.sqrt(0.5), (float) Math.sqrt(0.5), 0f);
|
private static final Quaternion BACK_ROT_NORM = new Quaternion(0f, -(float) Math.sqrt(0.5), (float) Math.sqrt(0.5), 0f);
|
||||||
|
|
||||||
private void applyBackTransform(Vector3f bonePosInChar, Quaternion boneRotInChar, ItemGrip grip) {
|
private void applyBackTransform(Vector3f bonePosInChar, Quaternion boneRotInChar, ItemGrip grip,
|
||||||
|
boolean twoHanded) {
|
||||||
Quaternion gripRot = (grip != null)
|
Quaternion gripRot = (grip != null)
|
||||||
? new Quaternion().fromAngles(DEG_TO_RAD * grip.getRotX(),
|
? new Quaternion().fromAngles(DEG_TO_RAD * grip.getRotX(),
|
||||||
DEG_TO_RAD * grip.getRotY(),
|
DEG_TO_RAD * grip.getRotY(),
|
||||||
DEG_TO_RAD * grip.getRotZ())
|
DEG_TO_RAD * grip.getRotZ())
|
||||||
: new Quaternion();
|
: new Quaternion();
|
||||||
// Klinge bei -Z erkannt (grip2PosZ > posZ) → 180° um X vorschalten, damit BACK_ROT_NORM
|
boolean bladeAtMinusZ = grip != null && (
|
||||||
// dieselbe Wirkung hat wie bei korrekt ausgerichtetem Modell (Klinge bei +Z).
|
(grip.getGrip2PosX() != 0f || grip.getGrip2PosY() != 0f || grip.getGrip2PosZ() != 0f)
|
||||||
boolean bladeAtMinusZ = grip != null && grip.getGrip2PosZ() != 0f && grip.getGrip2PosZ() > grip.getPosZ();
|
? grip.getGrip2PosZ() > grip.getPosZ()
|
||||||
|
: grip.getPosZ() > 0f);
|
||||||
Quaternion bladeFlip = bladeAtMinusZ
|
Quaternion bladeFlip = bladeAtMinusZ
|
||||||
? new Quaternion().fromAngles(com.jme3.math.FastMath.PI, 0f, 0f)
|
? new Quaternion().fromAngles(com.jme3.math.FastMath.PI, 0f, 0f)
|
||||||
: new Quaternion();
|
: new Quaternion();
|
||||||
|
|
||||||
Quaternion weaponRot = boneRotInChar.mult(BACK_ROT_NORM).mult(bladeFlip).mult(gripRot.inverse());
|
Quaternion weaponRot = boneRotInChar.mult(BACK_ROT_NORM).mult(bladeFlip).mult(gripRot.inverse());
|
||||||
Vector3f gripTarget = bonePosInChar.add(boneRotInChar.mult(BACK_GRIP_OFFSET));
|
Vector3f offset = twoHanded ? TWO_HAND_BACK_GRIP_OFFSET : BACK_GRIP_OFFSET;
|
||||||
|
Vector3f gripTarget = bonePosInChar.add(boneRotInChar.mult(offset));
|
||||||
|
// X=0: Waffenmitte (nicht handspezifischer Griffpunkt) auf Wirbelsäule zentrieren
|
||||||
Vector3f gripPos = (grip != null)
|
Vector3f gripPos = (grip != null)
|
||||||
? new Vector3f(grip.getPosX(), grip.getPosY(), grip.getPosZ())
|
? new Vector3f(0f, grip.getPosY(), grip.getPosZ())
|
||||||
: Vector3f.ZERO;
|
: Vector3f.ZERO;
|
||||||
Vector3f backPos = gripTarget.subtract(weaponRot.mult(gripPos));
|
Vector3f backPos = gripTarget.subtract(weaponRot.mult(gripPos));
|
||||||
if (bladeAtMinusZ) backPos = backPos.add(0.1f, 0f, 0f);
|
|
||||||
weaponSpatial.setLocalRotation(weaponRot);
|
weaponSpatial.setLocalRotation(weaponRot);
|
||||||
weaponSpatial.setLocalTranslation(backPos);
|
weaponSpatial.setLocalTranslation(backPos);
|
||||||
}
|
}
|
||||||
@@ -409,7 +419,9 @@ public class WeaponAttachmentControl extends AbstractControl {
|
|||||||
Vector3f boneInChar = charWorldRotInv.mult(
|
Vector3f boneInChar = charWorldRotInv.mult(
|
||||||
leftForeArmNode.getWorldTranslation().subtract(charWorldPos));
|
leftForeArmNode.getWorldTranslation().subtract(charWorldPos));
|
||||||
Quaternion boneRotInChar = charWorldRotInv.mult(leftForeArmNode.getWorldRotation());
|
Quaternion boneRotInChar = charWorldRotInv.mult(leftForeArmNode.getWorldRotation());
|
||||||
applyShieldArmTransform(boneInChar, boneRotInChar, grip);
|
ShieldCategoryAttachment shieldCfg = ShieldAttachmentIO.get(
|
||||||
|
shield != null ? shield.getSubCategory() : null);
|
||||||
|
applyShieldArmTransform(boneInChar, boneRotInChar, grip, shieldCfg);
|
||||||
shieldSpatial.setCullHint(Spatial.CullHint.Inherit);
|
shieldSpatial.setCullHint(Spatial.CullHint.Inherit);
|
||||||
} else if (!drawn && backNode != null) {
|
} else if (!drawn && backNode != null) {
|
||||||
Vector3f boneInChar = charWorldRotInv.mult(
|
Vector3f boneInChar = charWorldRotInv.mult(
|
||||||
@@ -422,17 +434,18 @@ public class WeaponAttachmentControl extends AbstractControl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void applyShieldArmTransform(Vector3f bonePosInChar, Quaternion boneRotInChar, ItemGrip grip) {
|
private void applyShieldArmTransform(Vector3f bonePosInChar, Quaternion boneRotInChar,
|
||||||
|
ItemGrip grip, ShieldCategoryAttachment cfg) {
|
||||||
Quaternion animDelta = (leftForeArmBindRotInChar != null)
|
Quaternion animDelta = (leftForeArmBindRotInChar != null)
|
||||||
? boneRotInChar.mult(leftForeArmBindRotInChar.inverse())
|
? boneRotInChar.mult(leftForeArmBindRotInChar.inverse())
|
||||||
: boneRotInChar;
|
: boneRotInChar;
|
||||||
Quaternion armRot = new Quaternion().fromAngles(SHIELD_ARM_ROT_X, SHIELD_ARM_ROT_Y, SHIELD_ARM_ROT_Z);
|
Quaternion armRot = new Quaternion().fromAngles(
|
||||||
|
DEG_TO_RAD * cfg.getRotX(), DEG_TO_RAD * cfg.getRotY(), DEG_TO_RAD * cfg.getRotZ());
|
||||||
Quaternion shieldRot = animDelta.mult(armRot);
|
Quaternion shieldRot = animDelta.mult(armRot);
|
||||||
Vector3f gripPos = (grip != null)
|
Vector3f gripPos = (grip != null)
|
||||||
? new Vector3f(grip.getPosX(), grip.getPosY(), grip.getPosZ())
|
? new Vector3f(grip.getPosX(), grip.getPosY(), grip.getPosZ())
|
||||||
: Vector3f.ZERO;
|
: Vector3f.ZERO;
|
||||||
// Ankerpunkt Richtung Ellbogen verschieben (lokale −Y des LeftHand-Bones)
|
Vector3f anchor = bonePosInChar.add(boneRotInChar.mult(new Vector3f(0f, -cfg.getElbowOffset(), 0f)));
|
||||||
Vector3f anchor = bonePosInChar.add(boneRotInChar.mult(new Vector3f(0f, -SHIELD_ARM_ELBOW_OFFSET, 0f)));
|
|
||||||
shieldSpatial.setLocalRotation(shieldRot);
|
shieldSpatial.setLocalRotation(shieldRot);
|
||||||
shieldSpatial.setLocalTranslation(anchor.subtract(shieldRot.mult(gripPos)));
|
shieldSpatial.setLocalTranslation(anchor.subtract(shieldRot.mult(gripPos)));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user