Zwischenstand
This commit is contained in:
Binary file not shown.
|
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 4.0 KiB |
@@ -125,12 +125,12 @@
|
||||
"TWO_HAND_BLOCK_STRAFE_LEFT": "Great Sword Strafe left",
|
||||
"TWO_HAND_BLOCK_STRAFE_RIGHT": "Great Sword Strafe Right",
|
||||
"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_2": "Great Sword Idle boring 2",
|
||||
"TWO_HAND_IDLE_BORED_3": "Great Sword Idle boring 3",
|
||||
"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",
|
||||
"animOffsets": {
|
||||
|
||||
Binary file not shown.
@@ -1,4 +1,4 @@
|
||||
#Sun Sep 20 15:31:32 CEST 2026
|
||||
#Thu Oct 15 15:05:26 CEST 2026
|
||||
attachedEmitters.count=0
|
||||
attachedLights.count=0
|
||||
attachedSounds.count=0
|
||||
|
||||
@@ -11,9 +11,9 @@
|
||||
"worthGold": 0,
|
||||
"damage": 0,
|
||||
"grip": {
|
||||
"posX": 0.11149749,
|
||||
"posY": 0.07795838,
|
||||
"posZ": 0.36503196,
|
||||
"posX": 0.1114975,
|
||||
"posY": 0.07676052,
|
||||
"posZ": -0.27503356,
|
||||
"rotX": 0.0,
|
||||
"rotY": 0.0,
|
||||
"rotZ": 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
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"SWORD": {
|
||||
"handOffX": 0.1,
|
||||
"handOffY": 0.0,
|
||||
"handOffZ": 0.05,
|
||||
"handOffX": 0.105,
|
||||
"handOffY": 0.1,
|
||||
"handOffZ": 0.5,
|
||||
"hand2OffX": 0.0,
|
||||
"hand2OffY": 0.0,
|
||||
"hand2OffZ": 0.0
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
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.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Lädt und speichert per-Kategorie Schild-Attachment-Konfiguration (shield_attachment.json).
|
||||
*/
|
||||
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 Map<String, ShieldCategoryAttachment> CONFIGS = new HashMap<>();
|
||||
private static boolean loaded = false;
|
||||
|
||||
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() {
|
||||
if (!loaded) loadFromClasspath();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -1258,6 +1258,18 @@ public class SharedInput {
|
||||
// ── 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. */
|
||||
public record SculptEdit(float screenX, float screenY, int action) {}
|
||||
public final ConcurrentLinkedQueue<SculptEdit> sculptEditQueue = new ConcurrentLinkedQueue<>();
|
||||
|
||||
@@ -121,6 +121,7 @@ public class ModelEditorState extends BaseAppState {
|
||||
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;
|
||||
@@ -260,9 +261,21 @@ public class ModelEditorState extends BaseAppState {
|
||||
guessGripPosition();
|
||||
}
|
||||
|
||||
// Modell laden – MUSS vor enableCharPreview stehen, damit currentPath aktuell ist
|
||||
String openPath = input.modelEditorOpenPath;
|
||||
if (openPath != null) {
|
||||
input.modelEditorOpenPath = null;
|
||||
mainModelPath = openPath;
|
||||
input.modelEditorLodPreview = 0;
|
||||
input.modelImportLod1PreviewNode.set(null);
|
||||
input.modelImportLod2PreviewNode.set(null);
|
||||
loadModel(openPath);
|
||||
}
|
||||
|
||||
// Charakter-Preview ein-/ausschalten
|
||||
boolean wantsPreview = (input.gripEditorCharPreview && input.gripEditorActive)
|
||||
|| input.attachmentEditorActive;
|
||||
|| input.attachmentEditorActive
|
||||
|| input.shieldEditorActive;
|
||||
if (wantsPreview && charPreviewNode == null) {
|
||||
enableCharPreview();
|
||||
} else if (!wantsPreview && charPreviewNode != null) {
|
||||
@@ -278,7 +291,7 @@ public class ModelEditorState extends BaseAppState {
|
||||
}
|
||||
}
|
||||
|
||||
// Waffe im Charakter-Preview jedes Frame neu positionieren
|
||||
// Waffe/Schild im Charakter-Preview jedes Frame neu positionieren
|
||||
if (charPreviewNode != null && charWeaponSpatial != null && charHandNode != null) {
|
||||
updateCharPreviewWeapon();
|
||||
}
|
||||
@@ -288,17 +301,6 @@ public class ModelEditorState extends BaseAppState {
|
||||
embedLodsAndSave(input.modelEditorEmbedLodsPath);
|
||||
}
|
||||
|
||||
// Modell laden
|
||||
String openPath = input.modelEditorOpenPath;
|
||||
if (openPath != null) {
|
||||
input.modelEditorOpenPath = null;
|
||||
mainModelPath = openPath;
|
||||
input.modelEditorLodPreview = 0;
|
||||
input.modelImportLod1PreviewNode.set(null);
|
||||
input.modelImportLod2PreviewNode.set(null);
|
||||
loadModel(openPath);
|
||||
}
|
||||
|
||||
// Invariante: wenn Charakter-Preview aktiv, standalone-Modell immer ausblenden
|
||||
if (charPreviewNode != null) {
|
||||
if (modelWrapper != null) modelWrapper.setCullHint(Spatial.CullHint.Always);
|
||||
@@ -2083,14 +2085,17 @@ public class ModelEditorState extends BaseAppState {
|
||||
|
||||
SkinningControl skin = findSkinCtrl(hero);
|
||||
if (skin != null) {
|
||||
charHandBindRot = extractJointBindRot(skin, "mixamorig:RightHand");
|
||||
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 clipName = previewAnimName(input.gripEditorSubCategory);
|
||||
String subCat = input.shieldEditorActive
|
||||
? input.shieldEditorSubCategory : input.gripEditorSubCategory;
|
||||
String clipName = previewAnimName(subCat);
|
||||
loadClipToComposer(anim, skin, clipName);
|
||||
}
|
||||
}
|
||||
@@ -2116,13 +2121,14 @@ public class ModelEditorState extends BaseAppState {
|
||||
private void disableCharPreview() {
|
||||
if (charPreviewNode != null) {
|
||||
charPreviewNode.removeFromParent();
|
||||
charPreviewNode = null;
|
||||
charWeaponSpatial = null;
|
||||
charHandNode = null;
|
||||
charLeftHandNode = null;
|
||||
charPalmNode = null;
|
||||
charHandBindRot = null;
|
||||
charPalmOffset = null;
|
||||
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);
|
||||
@@ -2130,6 +2136,10 @@ public class ModelEditorState extends BaseAppState {
|
||||
}
|
||||
|
||||
private void updateCharPreviewWeapon() {
|
||||
if (input.shieldEditorActive) {
|
||||
updateCharPreviewShield();
|
||||
return;
|
||||
}
|
||||
// Kategorie-Offsets aus SharedInput lesen (live-update)
|
||||
WeaponCategoryAttachment cfg = new WeaponCategoryAttachment();
|
||||
cfg.setHandOffX(input.gripEditorCatHandOffX);
|
||||
@@ -2205,11 +2215,39 @@ public class ModelEditorState extends BaseAppState {
|
||||
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" -> "Sword And Shield Idle";
|
||||
case "SWORD", "SHIELD" -> "Sword And Shield Idle";
|
||||
default -> "Idle";
|
||||
};
|
||||
}
|
||||
|
||||
@@ -114,12 +114,12 @@ public class AttachmentEditorController {
|
||||
// ── Panel ─────────────────────────────────────────────────────────────────
|
||||
|
||||
private VBox buildPanel(WeaponCategoryAttachment cfg, ItemSubCategory subCategory) {
|
||||
handOffXSpin = doubleSpin(cfg.getHandOffX(), -0.5, 0.5, 0.005, "m");
|
||||
handOffYSpin = doubleSpin(cfg.getHandOffY(), -0.5, 0.5, 0.005, "m");
|
||||
handOffZSpin = doubleSpin(cfg.getHandOffZ(), -0.5, 0.5, 0.005, "m");
|
||||
hand2OffXSpin = doubleSpin(cfg.getHand2OffX(), -0.5, 0.5, 0.005, "m");
|
||||
hand2OffYSpin = doubleSpin(cfg.getHand2OffY(), -0.5, 0.5, 0.005, "m");
|
||||
hand2OffZSpin = doubleSpin(cfg.getHand2OffZ(), -0.5, 0.5, 0.005, "m");
|
||||
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());
|
||||
|
||||
@@ -88,13 +88,15 @@ public class ContentController {
|
||||
// ──────────────────────────────────────────────────────────────────────────
|
||||
|
||||
private Runnable onEnterContentMode;
|
||||
private GripEditorController gripEditorController;
|
||||
private AttachmentEditorController attachmentEditorController;
|
||||
private GripEditorController gripEditorController;
|
||||
private AttachmentEditorController attachmentEditorController;
|
||||
private ShieldAttachmentEditorController shieldAttachmentEditorController;
|
||||
|
||||
public ContentController(ToolContext ctx) {
|
||||
this.ctx = ctx;
|
||||
this.gripEditorController = new GripEditorController(ctx);
|
||||
this.attachmentEditorController = new AttachmentEditorController(ctx);
|
||||
this.gripEditorController = new GripEditorController(ctx);
|
||||
this.attachmentEditorController = new AttachmentEditorController(ctx);
|
||||
this.shieldAttachmentEditorController = new ShieldAttachmentEditorController(ctx);
|
||||
}
|
||||
|
||||
// ── Öffentliche API ────────────────────────────────────────────────────────
|
||||
@@ -1024,6 +1026,18 @@ public class ContentController {
|
||||
});
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
private void buildItemViewInContent() {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -63,9 +63,14 @@ public class ItemEditorView extends BorderPane {
|
||||
void open(String modelPath, ItemSubCategory subCategory, ItemGrip grip);
|
||||
}
|
||||
|
||||
public interface ShieldAttachmentEditorLauncher {
|
||||
void open(String modelPath, ItemSubCategory subCategory, ItemGrip grip);
|
||||
}
|
||||
|
||||
private Runnable onIdChanged;
|
||||
private GripEditorLauncher gripEditorLauncher;
|
||||
private AttachmentEditorLauncher attachmentEditorLauncher;
|
||||
private GripEditorLauncher gripEditorLauncher;
|
||||
private AttachmentEditorLauncher attachmentEditorLauncher;
|
||||
private ShieldAttachmentEditorLauncher shieldAttachmentEditorLauncher;
|
||||
|
||||
private final PauseTransition savePause = new PauseTransition(Duration.millis(600));
|
||||
|
||||
@@ -96,6 +101,7 @@ public class ItemEditorView extends BorderPane {
|
||||
public void setOnIdChanged(Runnable r) { this.onIdChanged = r; }
|
||||
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 void selectItem(String itemId) {
|
||||
@@ -264,10 +270,16 @@ public class ItemEditorView extends BorderPane {
|
||||
shieldGripBtn.setStyle("-fx-background-color: #2a4a5a; -fx-text-fill: #aaddff; -fx-cursor: hand;");
|
||||
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,
|
||||
new Separator(),
|
||||
sectionTitle("Schild-Griff"),
|
||||
shieldGripBtn
|
||||
shieldGripBtn,
|
||||
shieldAttachmentBtn
|
||||
);
|
||||
shieldSection.setVisible(false);
|
||||
shieldSection.setManaged(false);
|
||||
@@ -358,6 +370,13 @@ public class ItemEditorView extends BorderPane {
|
||||
});
|
||||
}
|
||||
|
||||
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() {
|
||||
ItemCategory cat = catCombo.getValue();
|
||||
ItemSubCategory sub = subCatCombo.getValue();
|
||||
|
||||
@@ -12,6 +12,8 @@ import com.jme3.scene.control.AbstractControl;
|
||||
import de.blight.common.model.Item;
|
||||
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.common.model.WeaponAttachmentIO;
|
||||
import de.blight.common.model.WeaponCategoryAttachment;
|
||||
import de.blight.game.animation.RetargetingSystem;
|
||||
@@ -35,7 +37,8 @@ public class NpcWeaponAttachmentControl extends AbstractControl {
|
||||
private static final float DEG_TO_RAD = (float) Math.PI / 180f;
|
||||
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(
|
||||
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);
|
||||
@@ -157,8 +160,10 @@ public class NpcWeaponAttachmentControl extends AbstractControl {
|
||||
applyHandTransform(weaponSpatial, attachInChar, boneRotInChar, wGrip, cfg);
|
||||
}
|
||||
} else {
|
||||
boolean twoHanded = weaponItem != null
|
||||
&& weaponItem.getSubCategory() == ItemSubCategory.TWO_HANDED_SWORD;
|
||||
applyBackTransform(weaponSpatial, boneInChar, boneRotInChar,
|
||||
weaponItem != null ? weaponItem.getGrip() : null);
|
||||
weaponItem != null ? weaponItem.getGrip() : null, twoHanded);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -169,7 +174,9 @@ public class NpcWeaponAttachmentControl extends AbstractControl {
|
||||
if (drawn && leftForeArmNode != null) {
|
||||
Vector3f boneInChar = charWorldRotInv.mult(leftForeArmNode.getWorldTranslation().subtract(charWorldPos));
|
||||
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);
|
||||
} else if (!drawn && backNode != null) {
|
||||
Vector3f boneInChar = charWorldRotInv.mult(backNode.getWorldTranslation().subtract(charWorldPos));
|
||||
@@ -275,7 +282,7 @@ 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 void applyBackTransform(Spatial s, Vector3f boneInChar,
|
||||
Quaternion boneRotInChar, ItemGrip grip) {
|
||||
Quaternion boneRotInChar, ItemGrip grip, boolean twoHanded) {
|
||||
Quaternion gripRot = (grip != null)
|
||||
? new Quaternion().fromAngles(DEG_TO_RAD * grip.getRotX(),
|
||||
DEG_TO_RAD * grip.getRotY(),
|
||||
@@ -286,7 +293,8 @@ public class NpcWeaponAttachmentControl extends AbstractControl {
|
||||
? new Quaternion().fromAngles(com.jme3.math.FastMath.PI, 0f, 0f)
|
||||
: new Quaternion();
|
||||
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));
|
||||
Vector3f gripPos = grip != null
|
||||
? new Vector3f(grip.getPosX(), grip.getPosY(), grip.getPosZ()) : Vector3f.ZERO;
|
||||
Vector3f backPos = target.subtract(weaponRot.mult(gripPos));
|
||||
@@ -298,15 +306,17 @@ public class NpcWeaponAttachmentControl extends AbstractControl {
|
||||
// ── Schild-Transforms (1:1 von WeaponAttachmentControl) ──────────────────────
|
||||
|
||||
private void applyShieldArmTransform(Spatial s, Vector3f bonePosInChar,
|
||||
Quaternion boneRotInChar, ItemGrip grip) {
|
||||
Quaternion boneRotInChar, ItemGrip grip,
|
||||
ShieldCategoryAttachment cfg) {
|
||||
Quaternion animDelta = (leftForeArmBindRotInChar != null)
|
||||
? boneRotInChar.mult(leftForeArmBindRotInChar.inverse())
|
||||
: 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);
|
||||
Vector3f gripPos = (grip != null)
|
||||
? 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.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.bullet.control.CharacterControl;
|
||||
import com.jme3.input.InputManager;
|
||||
import com.jme3.input.KeyInput;
|
||||
import com.jme3.input.MouseInput;
|
||||
import com.jme3.input.RawInputListener;
|
||||
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_ROLL = "Roll";
|
||||
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 = {
|
||||
"Forward", "Backward", "Left", "Right", "Jump", "Sprint", "Walk",
|
||||
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;
|
||||
@@ -381,8 +385,11 @@ public class PlayerInputControl implements CombatStyleBase.AnimReceiver {
|
||||
}
|
||||
}
|
||||
case ACT_COMBAT_ALT -> handleCombatAlt(isPressed);
|
||||
case ACT_ROLL -> { if (isPressed) triggerRollFromInput(); }
|
||||
case ACT_SNEAK -> { if (!weaponDrawn) handleSneakKey(isPressed); }
|
||||
case ACT_ROLL -> { if (isPressed) triggerRollFromInput(); }
|
||||
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); }
|
||||
}
|
||||
};
|
||||
|
||||
@@ -468,6 +475,7 @@ public class PlayerInputControl implements CombatStyleBase.AnimReceiver {
|
||||
|
||||
public void setMainCharacter(MainCharacter mc) { this.mainCharacter = mc; }
|
||||
public boolean isWeaponDrawn() { return weaponDrawn; }
|
||||
public boolean isDrawAnimActive() { return drawAnimActive; }
|
||||
public ItemSubCategory getDrawnWeaponType() { return drawnWeaponType; }
|
||||
public boolean isLastAttackCritical() { return lastAttackCritical; }
|
||||
public com.jme3.anim.SkinningControl getSkinningControl() { return skinningControl; }
|
||||
@@ -832,6 +840,9 @@ public class PlayerInputControl implements CombatStyleBase.AnimReceiver {
|
||||
inputManager.addMapping(ACT_COMBAT_ALT, new MouseButtonTrigger(MouseInput.BUTTON_RIGHT));
|
||||
inputManager.addMapping(ACT_ROLL, new KeyTrigger(kb.roll));
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -1222,7 +1233,8 @@ public class PlayerInputControl implements CombatStyleBase.AnimReceiver {
|
||||
} else {
|
||||
boolean swordShield = drawnWeaponType == de.blight.common.model.ItemSubCategory.SWORD
|
||||
&& hasShieldEquipped();
|
||||
if (swordShield) {
|
||||
boolean twoHand = drawnWeaponType == ItemSubCategory.TWO_HANDED_SWORD;
|
||||
if (swordShield || twoHand) {
|
||||
if (boredAnimRemaining > 0f) {
|
||||
boredAnimRemaining -= tpf;
|
||||
target = boredVariant;
|
||||
@@ -1231,9 +1243,15 @@ public class PlayerInputControl implements CombatStyleBase.AnimReceiver {
|
||||
if (boredIdleTimer >= BORED_IDLE_DELAY) {
|
||||
boredIdleTimer = 0f;
|
||||
int pick = playerRng.nextInt(3);
|
||||
boredVariant = pick == 0 ? AnimationAction.SWORD_IDLE_BORED
|
||||
: pick == 1 ? AnimationAction.SWORD_IDLE_BORED_2
|
||||
: AnimationAction.SWORD_IDLE_BORED_3;
|
||||
if (swordShield) {
|
||||
boredVariant = pick == 0 ? AnimationAction.SWORD_IDLE_BORED
|
||||
: pick == 1 ? AnimationAction.SWORD_IDLE_BORED_2
|
||||
: 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);
|
||||
target = boredVariant;
|
||||
} else {
|
||||
@@ -1676,6 +1694,25 @@ public class PlayerInputControl implements CombatStyleBase.AnimReceiver {
|
||||
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() {
|
||||
if (!weaponDrawn) return;
|
||||
if (rollActive || pickupActive || blockingAnimActive || turnActive
|
||||
|
||||
@@ -13,6 +13,8 @@ import de.blight.common.model.Item;
|
||||
import de.blight.common.model.ItemGrip;
|
||||
import de.blight.common.model.ItemSubCategory;
|
||||
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;
|
||||
@@ -38,7 +40,8 @@ public class WeaponAttachmentControl extends AbstractControl {
|
||||
private static final float DEG_TO_RAD = (float) Math.PI / 180f;
|
||||
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(
|
||||
ItemGrip.HAND_GRIP_OFFSET_X, ItemGrip.HAND_GRIP_OFFSET_Y, ItemGrip.HAND_GRIP_OFFSET_Z);
|
||||
|
||||
@@ -196,14 +199,16 @@ public class WeaponAttachmentControl extends AbstractControl {
|
||||
: boneInChar;
|
||||
boolean usesTwoHand = isTwoHand && leftForeArmNode != 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) {
|
||||
applyTwoHandTransform(attachInChar, boneRotInChar, grip, charWorldRotInv, charWorldPos, cfg);
|
||||
} else {
|
||||
applyHandTransform(attachInChar, boneRotInChar, grip, cfg);
|
||||
}
|
||||
} else {
|
||||
applyBackTransform(boneInChar, boneRotInChar, grip);
|
||||
boolean twoHanded = item != null && item.getSubCategory() == ItemSubCategory.TWO_HANDED_SWORD;
|
||||
applyBackTransform(boneInChar, boneRotInChar, grip, twoHanded);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -356,7 +361,8 @@ public class WeaponAttachmentControl extends AbstractControl {
|
||||
// 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 void applyBackTransform(Vector3f bonePosInChar, Quaternion boneRotInChar, ItemGrip grip) {
|
||||
private void applyBackTransform(Vector3f bonePosInChar, Quaternion boneRotInChar, ItemGrip grip,
|
||||
boolean twoHanded) {
|
||||
Quaternion gripRot = (grip != null)
|
||||
? new Quaternion().fromAngles(DEG_TO_RAD * grip.getRotX(),
|
||||
DEG_TO_RAD * grip.getRotY(),
|
||||
@@ -369,7 +375,8 @@ public class WeaponAttachmentControl extends AbstractControl {
|
||||
? new Quaternion().fromAngles(com.jme3.math.FastMath.PI, 0f, 0f)
|
||||
: new Quaternion();
|
||||
Quaternion weaponRot = boneRotInChar.mult(BACK_ROT_NORM).mult(bladeFlip).mult(gripRot.inverse());
|
||||
Vector3f gripTarget = bonePosInChar.add(boneRotInChar.mult(BACK_GRIP_OFFSET));
|
||||
Vector3f gripTarget = bonePosInChar.add(boneRotInChar.mult(
|
||||
twoHanded ? TWO_HAND_BACK_GRIP_OFFSET : BACK_GRIP_OFFSET));
|
||||
Vector3f gripPos = (grip != null)
|
||||
? new Vector3f(grip.getPosX(), grip.getPosY(), grip.getPosZ())
|
||||
: Vector3f.ZERO;
|
||||
@@ -406,7 +413,9 @@ public class WeaponAttachmentControl extends AbstractControl {
|
||||
Vector3f boneInChar = charWorldRotInv.mult(
|
||||
leftForeArmNode.getWorldTranslation().subtract(charWorldPos));
|
||||
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);
|
||||
} else if (!drawn && backNode != null) {
|
||||
Vector3f boneInChar = charWorldRotInv.mult(
|
||||
@@ -419,17 +428,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)
|
||||
? boneRotInChar.mult(leftForeArmBindRotInChar.inverse())
|
||||
: 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);
|
||||
Vector3f gripPos = (grip != null)
|
||||
? new Vector3f(grip.getPosX(), grip.getPosY(), grip.getPosZ())
|
||||
: Vector3f.ZERO;
|
||||
// Ankerpunkt Richtung Ellbogen verschieben (lokale −Y des LeftHand-Bones)
|
||||
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)));
|
||||
shieldSpatial.setLocalRotation(shieldRot);
|
||||
shieldSpatial.setLocalTranslation(anchor.subtract(shieldRot.mult(gripPos)));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user