Kampfsystem für Zweihänder erweitert

This commit is contained in:
2026-10-06 21:48:44 +02:00
parent 62931e3273
commit 9ff911eaa6
54 changed files with 258 additions and 47 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -37,7 +37,30 @@
"Sword And Shield Idle boring 3", "Sword And Shield Idle boring 3",
"Sword And Shield Jump Running", "Sword And Shield Jump Running",
"Sword And Shield Jump", "Sword And Shield Jump",
"Sword And Shield Slash" "Sword And Shield Slash",
"Draw A Great Sword",
"Great Sword Attack 1",
"Great Sword Attack 2",
"Great Sword Attack 3",
"Great Sword Block Impact",
"Great Sword Blocking Idle",
"Great Sword Blocking to Idle",
"Great Sword Heavy Attack",
"Great Sword Idle",
"Great Sword Idle boring",
"Great Sword Idle boring 2",
"Great Sword Idle boring 3",
"Great Sword Idle to Blocking",
"Great Sword Impact",
"Great Sword Jump",
"Great Sword Parade",
"Great Sword Run",
"Great Sword Running Jump",
"Great Sword Strafe Right",
"Great Sword Strafe left",
"Great Sword Walk",
"Great Sword Walk backwards",
"Sheath A Great Sword"
], ],
"actionMap": { "actionMap": {
"DEFAULT": "Idle", "DEFAULT": "Idle",
@@ -82,7 +105,32 @@
"SWORD_IDLE_BORED": "Sword And Shield Idle boring", "SWORD_IDLE_BORED": "Sword And Shield Idle boring",
"SWORD_IDLE_BORED_2": "Sword And Shield Idle boring 2", "SWORD_IDLE_BORED_2": "Sword And Shield Idle boring 2",
"SWORD_IDLE_BORED_3": "Sword And Shield Idle boring 3", "SWORD_IDLE_BORED_3": "Sword And Shield Idle boring 3",
"STAND_TO_CROUCH": "Standing To Crouched" "STAND_TO_CROUCH": "Standing To Crouched",
"TWO_HAND_DRAW": "Draw A Great Sword",
"TWO_HAND_SHEATHE": "Sheath A Great Sword",
"TWO_HAND_BLOCK": "Great Sword Blocking Idle",
"TWO_HAND_ATTACK_1": "Great Sword Attack 1",
"TWO_HAND_ATTACK_2": "Great Sword Attack 2",
"TWO_HAND_ATTACK_3": "Great Sword Attack 3",
"TWO_HAND_HEAVY_ATTACK": "Great Sword Heavy Attack",
"TWO_HAND_IDLE_TO_BLOCK": "Great Sword Idle to Blocking",
"TWO_HAND_PARADE": "Great Sword Parade",
"TWO_HAND_BLOCK_IMPACT": "Great Sword Block Impact",
"TWO_HAND_IDLE_IMPACT": "Great Sword Impact",
"TWO_HAND_IDLE": "Great Sword Idle",
"TWO_HAND_WALK": "Great Sword Walk",
"TWO_HAND_RUN": "Great Sword Run",
"TWO_HAND_BLOCK_MOVE": "Great Sword Walk",
"TWO_HAND_BLOCK_MOVE_BACKWARD": "Great Sword Walk backwards",
"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"
}, },
"previewModelPath": "Characters/Models/hero.j3o", "previewModelPath": "Characters/Models/hero.j3o",
"animOffsets": { "animOffsets": {

View File

@@ -664,18 +664,10 @@ public class AnimPreviewState extends BaseAppState {
// snap passiert ausschließlich zur Laufzeit im Spiel via AnimationLibrary. // snap passiert ausschließlich zur Laufzeit im Spiel via AnimationLibrary.
boolean isJ3oSource = animAssetPath.matches("(?i).*\\.j3o$"); boolean isJ3oSource = animAssetPath.matches("(?i).*\\.j3o$");
// FBX-Import erfordert ein geladenes Charakter-Modell. // Kein Charakter-Modell geladen: Normalisierung gegen eigenes Skelett (Orphan-Fold + Scale-Strip).
// Ohne Ziel-Armatur kann normalizeAndRetargetClip die $AssimpFbx$_Translation-Orphan-Joints // Das erzeugte j3o ist später per redirectAnimClipByName auf jedes kompatible Rig anwendbar.
// nicht in den Hips-Track falten → Hips-Translation geht verloren → Charakter schrumpft. com.jme3.anim.Armature effectiveDst = dstArm != null ? dstArm : srcArm;
if (!isJ3oSource && srcArm != null && dstArm == null) { com.jme3.anim.Armature snapArm = effectiveDst;
input.animPreviewStatus =
"Fehler: FBX-Import erfordert ein geladenes Charakter-Modell. " +
"Bitte zuerst das Modell laden, dann die Animation importieren.";
LOG.warn("[AnimPreview] FBX-Import ohne Charakter-Modell abgebrochen: {}", animAssetPath);
return;
}
com.jme3.anim.Armature snapArm = dstArm != null ? dstArm : srcArm;
int saved = 0; int saved = 0;
for (AnimClip clip : sourceAC.getAnimClips()) { for (AnimClip clip : sourceAC.getAnimClips()) {
@@ -688,7 +680,7 @@ public class AnimPreviewState extends BaseAppState {
} }
} }
AnimClip result; AnimClip result;
if (!retarget) { if (!retarget && (isJ3oSource || srcArm == null)) {
result = clip; result = clip;
} else if (isJ3oSource) { } else if (isJ3oSource) {
// j3o-Clips wurden bereits beim FBX-Import normalisiert (in Modell-Raum gespeichert). // j3o-Clips wurden bereits beim FBX-Import normalisiert (in Modell-Raum gespeichert).
@@ -696,12 +688,15 @@ public class AnimPreviewState extends BaseAppState {
result = redirectAnimClipByName(clip, dstArm); result = redirectAnimClipByName(clip, dstArm);
} else { } else {
// FBX-Import: Normalisierung in Modell-Raum (Orphan-Fold + Bind-Pose-Delta). // FBX-Import: Normalisierung in Modell-Raum (Orphan-Fold + Bind-Pose-Delta).
// Das resultierende j3o ist dann mit plain redirectAnimClipByName kompatibel. // Kein Charakter-Modell → effectiveDst == srcArm → reine Normalisierung ohne Retarget.
result = normalizeAndRetargetClip(clip, dstArm); result = normalizeAndRetargetClip(clip, effectiveDst);
if (result == null) { if (result == null) {
LOG.warn("[AnimPreview] normalizeAndRetarget: keine Joints gemappt, versuche Retargeting"); LOG.warn("[AnimPreview] normalizeAndRetarget: keine Joints gemappt, versuche Retargeting");
result = de.blight.game.animation.RetargetingSystem.retarget(clip, srcArm, dstArm); if (dstArm != null) {
result = de.blight.game.animation.RetargetingSystem.retarget(clip, srcArm, dstArm);
}
} }
if (result == null) result = clip;
} }
if (result == null) continue; if (result == null) continue;

View File

@@ -997,6 +997,11 @@ public class AnimSetController implements ToolController {
return; return;
} }
// ── Aktions-Combo mit Suchfeld ────────────────────────────────────────
javafx.scene.control.TextField actionSearch = new javafx.scene.control.TextField();
actionSearch.setPromptText("Aktion suchen…");
actionSearch.setMaxWidth(Double.MAX_VALUE);
ComboBox<de.blight.game.animation.AnimationAction> actionCombo = new ComboBox<>(); ComboBox<de.blight.game.animation.AnimationAction> actionCombo = new ComboBox<>();
actionCombo.getItems().addAll(available); actionCombo.getItems().addAll(available);
javafx.util.Callback<javafx.scene.control.ListView<de.blight.game.animation.AnimationAction>, javafx.util.Callback<javafx.scene.control.ListView<de.blight.game.animation.AnimationAction>,
@@ -1013,29 +1018,85 @@ public class AnimSetController implements ToolController {
actionCombo.setMaxWidth(Double.MAX_VALUE); actionCombo.setMaxWidth(Double.MAX_VALUE);
actionCombo.getSelectionModel().selectFirst(); actionCombo.getSelectionModel().selectFirst();
ComboBox<String> clipCombo = new ComboBox<>(); actionSearch.textProperty().addListener((obs, ov, nv) -> {
String lower = nv == null ? "" : nv.toLowerCase();
actionCombo.getItems().setAll(available.stream()
.filter(a -> lower.isEmpty()
|| a.displayName().toLowerCase().contains(lower)
|| a.name().toLowerCase().contains(lower))
.collect(java.util.stream.Collectors.toList()));
if (!actionCombo.getItems().isEmpty()) actionCombo.getSelectionModel().selectFirst();
});
// ── Clip-Combo mit Suchfeld ───────────────────────────────────────────
java.util.List<String> allClipNames = new java.util.ArrayList<>(animSetClipListView.getItems()); java.util.List<String> allClipNames = new java.util.ArrayList<>(animSetClipListView.getItems());
for (String subName : editableSubClips.keySet()) { for (String subName : editableSubClips.keySet()) {
if (!allClipNames.contains(subName)) allClipNames.add(subName); if (!allClipNames.contains(subName)) allClipNames.add(subName);
} }
javafx.scene.control.TextField clipSearch = new javafx.scene.control.TextField();
clipSearch.setPromptText("Clip suchen…");
clipSearch.setMaxWidth(Double.MAX_VALUE);
ComboBox<String> clipCombo = new ComboBox<>();
clipCombo.getItems().addAll(allClipNames); clipCombo.getItems().addAll(allClipNames);
clipCombo.setMaxWidth(Double.MAX_VALUE); clipCombo.setMaxWidth(Double.MAX_VALUE);
clipCombo.getSelectionModel().selectFirst(); clipCombo.getSelectionModel().selectFirst();
clipSearch.textProperty().addListener((obs, ov, nv) -> {
String lower = nv == null ? "" : nv.toLowerCase();
clipCombo.getItems().setAll(allClipNames.stream()
.filter(c -> lower.isEmpty() || c.toLowerCase().contains(lower))
.collect(java.util.stream.Collectors.toList()));
if (!clipCombo.getItems().isEmpty()) clipCombo.getSelectionModel().selectFirst();
});
// ── Layout ────────────────────────────────────────────────────────────
VBox actionBox = new VBox(4, actionSearch, actionCombo);
VBox clipBox = new VBox(4, clipSearch, clipCombo);
javafx.scene.layout.GridPane grid = new javafx.scene.layout.GridPane(); javafx.scene.layout.GridPane grid = new javafx.scene.layout.GridPane();
grid.setHgap(8); grid.setVgap(6); grid.setHgap(8); grid.setVgap(8);
grid.add(new Label("Aktion:"), 0, 0); grid.add(actionCombo, 1, 0); grid.add(new Label("Aktion:"), 0, 0); grid.add(actionBox, 1, 0);
grid.add(new Label("Clip:"), 0, 1); grid.add(clipCombo, 1, 1); grid.add(new Label("Clip:"), 0, 1); grid.add(clipBox, 1, 1);
javafx.scene.layout.ColumnConstraints cc = new javafx.scene.layout.ColumnConstraints(); javafx.scene.layout.ColumnConstraints cc = new javafx.scene.layout.ColumnConstraints();
cc.setHgrow(Priority.ALWAYS); cc.setHgrow(Priority.ALWAYS);
grid.getColumnConstraints().addAll(new javafx.scene.layout.ColumnConstraints(), cc); grid.getColumnConstraints().addAll(new javafx.scene.layout.ColumnConstraints(), cc);
CheckBox weitereCB = new CheckBox("Weitere zuordnen");
VBox content = new VBox(12, grid, weitereCB);
Dialog<ButtonType> dlg = new Dialog<>(); Dialog<ButtonType> dlg = new Dialog<>();
dlg.initOwner(ctx.stageSupplier().get()); dlg.initOwner(ctx.stageSupplier().get());
dlg.setTitle("Aktion zuordnen"); dlg.setTitle("Aktion zuordnen");
ButtonType ok = new ButtonType("Hinzufügen", ButtonBar.ButtonData.OK_DONE); ButtonType ok = new ButtonType("Hinzufügen", ButtonBar.ButtonData.OK_DONE);
dlg.getDialogPane().getButtonTypes().addAll(ok, ButtonType.CANCEL); dlg.getDialogPane().getButtonTypes().addAll(ok, ButtonType.CANCEL);
dlg.getDialogPane().setContent(grid); dlg.getDialogPane().setContent(content);
dlg.getDialogPane().setPrefWidth(480);
// Wenn "Weitere zuordnen" aktiv: Dialog offen lassen, nächste Aktion auto-selektieren.
javafx.scene.Node okNode = dlg.getDialogPane().lookupButton(ok);
okNode.addEventFilter(javafx.event.ActionEvent.ACTION, ev -> {
if (!weitereCB.isSelected()) return;
ev.consume();
var action = actionCombo.getValue();
var clip = clipCombo.getValue();
if (action == null || clip == null) return;
String entry = action.name() + " → " + clip;
if (!animSetActionListView.getItems().contains(entry)) {
animSetActionListView.getItems().add(entry);
autoSaveAnimSet();
}
available.remove(action);
String lower = actionSearch.getText() == null ? "" : actionSearch.getText().toLowerCase();
actionCombo.getItems().setAll(available.stream()
.filter(a -> lower.isEmpty()
|| a.displayName().toLowerCase().contains(lower)
|| a.name().toLowerCase().contains(lower))
.collect(java.util.stream.Collectors.toList()));
if (!actionCombo.getItems().isEmpty()) actionCombo.getSelectionModel().selectFirst();
});
dlg.showAndWait().ifPresent(bt -> { dlg.showAndWait().ifPresent(bt -> {
if (bt != ok) return; if (bt != ok) return;
var action = actionCombo.getValue(); var action = actionCombo.getValue();

View File

@@ -53,10 +53,13 @@ public enum AnimationAction {
AXE_IDLE, AXE_WALK, AXE_RUN, AXE_IDLE, AXE_WALK, AXE_RUN,
// ── Zweihänder ──────────────────────────────────────────────────────────── // ── Zweihänder ────────────────────────────────────────────────────────────
TWO_HAND_DRAW, TWO_HAND_SHEATHE, TWO_HAND_BLOCK, TWO_HAND_DRAW, TWO_HAND_SHEATHE, TWO_HAND_BLOCK,
TWO_HAND_ATTACK_1, TWO_HAND_ATTACK_2, TWO_HAND_ATTACK_3, TWO_HAND_HEAVY_ATTACK, TWO_HAND_ATTACK_1, TWO_HAND_ATTACK_2, TWO_HAND_ATTACK_3, TWO_HAND_HEAVY_ATTACK,
TWO_HAND_IDLE_TO_BLOCK, TWO_HAND_PARADE, TWO_HAND_BLOCK_IMPACT, TWO_HAND_IDLE_IMPACT, TWO_HAND_IDLE_TO_BLOCK, TWO_HAND_PARADE, TWO_HAND_BLOCK_IMPACT, TWO_HAND_IDLE_IMPACT,
TWO_HAND_IDLE, TWO_HAND_WALK, TWO_HAND_RUN, TWO_HAND_IDLE, TWO_HAND_WALK, TWO_HAND_RUN, TWO_HAND_JUMP, TWO_HAND_RUNNING_JUMP,
TWO_HAND_IDLE_BORED, TWO_HAND_IDLE_BORED_2, TWO_HAND_IDLE_BORED_3,
TWO_HAND_DEATH, TWO_HAND_DEATH_2,
TWO_HAND_BLOCK_MOVE, TWO_HAND_BLOCK_MOVE_BACKWARD, TWO_HAND_BLOCK_STRAFE_LEFT, TWO_HAND_BLOCK_STRAFE_RIGHT,
// ── Bogen ───────────────────────────────────────────────────────────────── // ── Bogen ─────────────────────────────────────────────────────────────────
BOW_DRAW, BOW_SHEATHE, BOW_HIT_TAKEN, BOW_DRAW, BOW_SHEATHE, BOW_HIT_TAKEN,
@@ -157,8 +160,19 @@ public enum AnimationAction {
case TWO_HAND_BLOCK_IMPACT -> TextRegistry.resolve(null, key, "Block-Treffer (Zweihänder)"); case TWO_HAND_BLOCK_IMPACT -> TextRegistry.resolve(null, key, "Block-Treffer (Zweihänder)");
case TWO_HAND_IDLE_IMPACT -> TextRegistry.resolve(null, key, "Treffer (Zweihänder)"); case TWO_HAND_IDLE_IMPACT -> TextRegistry.resolve(null, key, "Treffer (Zweihänder)");
case TWO_HAND_IDLE -> TextRegistry.resolve(null, key, "Stehen (Zweihänder)"); case TWO_HAND_IDLE -> TextRegistry.resolve(null, key, "Stehen (Zweihänder)");
case TWO_HAND_WALK -> TextRegistry.resolve(null, key, "Gehen (Zweihänder)"); case TWO_HAND_WALK -> TextRegistry.resolve(null, key, "Gehen (Zweihänder)");
case TWO_HAND_RUN -> TextRegistry.resolve(null, key, "Laufen (Zweihänder)"); case TWO_HAND_RUN -> TextRegistry.resolve(null, key, "Laufen (Zweihänder)");
case TWO_HAND_JUMP -> TextRegistry.resolve(null, key, "Sprung (Zweihänder)");
case TWO_HAND_RUNNING_JUMP -> TextRegistry.resolve(null, key, "Sprung Laufend (Zweihänder)");
case TWO_HAND_IDLE_BORED -> TextRegistry.resolve(null, key, "Idle Gelangweilt (Zweihänder)");
case TWO_HAND_IDLE_BORED_2 -> TextRegistry.resolve(null, key, "Idle Gelangweilt 2 (Zweihänder)");
case TWO_HAND_IDLE_BORED_3 -> TextRegistry.resolve(null, key, "Idle Gelangweilt 3 (Zweihänder)");
case TWO_HAND_DEATH -> TextRegistry.resolve(null, key, "Tod (Zweihänder)");
case TWO_HAND_DEATH_2 -> TextRegistry.resolve(null, key, "Tod 2 (Zweihänder)");
case TWO_HAND_BLOCK_MOVE -> TextRegistry.resolve(null, key, "Block Vorwärts (Zweihänder)");
case TWO_HAND_BLOCK_MOVE_BACKWARD -> TextRegistry.resolve(null, key, "Block Rückwärts (Zweihänder)");
case TWO_HAND_BLOCK_STRAFE_LEFT -> TextRegistry.resolve(null, key, "Block Strafe Links (Zweihänder)");
case TWO_HAND_BLOCK_STRAFE_RIGHT -> TextRegistry.resolve(null, key, "Block Strafe Rechts (Zweihänder)");
case BOW_DRAW -> TextRegistry.resolve(null, key, "Ziehen (Bogen)"); case BOW_DRAW -> TextRegistry.resolve(null, key, "Ziehen (Bogen)");
case BOW_SHEATHE -> TextRegistry.resolve(null, key, "Wegstecken (Bogen)"); case BOW_SHEATHE -> TextRegistry.resolve(null, key, "Wegstecken (Bogen)");

View File

@@ -31,9 +31,8 @@ public final class SwordShieldCombat extends CombatStyleBase {
public static AnimationAction resolveIdleImpactAction(ItemSubCategory weaponType) { public static AnimationAction resolveIdleImpactAction(ItemSubCategory weaponType) {
if (weaponType == null) { return AnimationAction.SWORD_IDLE_IMPACT; } if (weaponType == null) { return AnimationAction.SWORD_IDLE_IMPACT; }
return switch (weaponType) { return switch (weaponType) {
case AXE -> AnimationAction.AXE_IDLE_IMPACT; case AXE -> AnimationAction.AXE_IDLE_IMPACT;
case TWO_HANDED_SWORD -> AnimationAction.TWO_HAND_IDLE_IMPACT; default -> AnimationAction.SWORD_IDLE_IMPACT;
default -> AnimationAction.SWORD_IDLE_IMPACT;
}; };
} }
@@ -44,9 +43,8 @@ public final class SwordShieldCombat extends CombatStyleBase {
public static AnimationAction resolveBlockImpactAction(ItemSubCategory weaponType) { public static AnimationAction resolveBlockImpactAction(ItemSubCategory weaponType) {
if (weaponType == null) { return AnimationAction.SWORD_BLOCK_IMPACT; } if (weaponType == null) { return AnimationAction.SWORD_BLOCK_IMPACT; }
return switch (weaponType) { return switch (weaponType) {
case AXE -> AnimationAction.AXE_BLOCK_IMPACT; case AXE -> AnimationAction.AXE_BLOCK_IMPACT;
case TWO_HANDED_SWORD -> AnimationAction.TWO_HAND_BLOCK_IMPACT; default -> AnimationAction.SWORD_BLOCK_IMPACT;
default -> AnimationAction.SWORD_BLOCK_IMPACT;
}; };
} }
@@ -57,9 +55,8 @@ public final class SwordShieldCombat extends CombatStyleBase {
public static AnimationAction resolveParadeAction(ItemSubCategory weaponType) { public static AnimationAction resolveParadeAction(ItemSubCategory weaponType) {
if (weaponType == null) { return AnimationAction.SWORD_PARADE; } if (weaponType == null) { return AnimationAction.SWORD_PARADE; }
return switch (weaponType) { return switch (weaponType) {
case AXE -> AnimationAction.AXE_PARADE; case AXE -> AnimationAction.AXE_PARADE;
case TWO_HANDED_SWORD -> AnimationAction.TWO_HAND_PARADE; default -> AnimationAction.SWORD_PARADE;
default -> AnimationAction.SWORD_PARADE;
}; };
} }

View File

@@ -0,0 +1,68 @@
package de.blight.game.combat.twohanded;
import de.blight.game.animation.AnimationAction;
import de.blight.game.combat.CombatStyleBase;
/**
* Gemeinsame Kampflogik für den Zweihänder-Stil.
*
* Wird von beiden Seiten genutzt:
* – {@code PlayerInputControl} (Held)
* – {@code WorldNpcsState} (Gegner-KI)
*
* Alle Animations-Auflösungen (welche Animation für welches Ereignis?) und
* alle Reaktions-Anwendungen (Impact, Stagger, Konterparade) sind hier
* zentralisiert. Bug-Fixes gelten damit automatisch für Held und NPC.
*
* Neuer Kampfstil: erbt von {@link CombatStyleBase} und folgt demselben
* Muster in einem eigenen Unter-Package.
*/
public final class TwoHandedCombat extends CombatStyleBase {
private TwoHandedCombat() {}
// ── Animations-Auflösung ──────────────────────────────────────────────────
/**
* Impact-Animation in Idle/Kampf-Haltung (Entität wurde getroffen).
* Identisch für Spieler und NPC.
*/
public static AnimationAction resolveIdleImpactAction() {
return AnimationAction.TWO_HAND_IDLE_IMPACT;
}
/**
* Block-Impact-Animation (Entität hat einen Treffer abgeblockt).
* Identisch für Spieler und NPC.
*/
public static AnimationAction resolveBlockImpactAction() {
return AnimationAction.TWO_HAND_BLOCK_IMPACT;
}
/**
* Konterangriff-Animation nach erfolgreicher Parade.
* Identisch für Spieler und NPC.
*/
public static AnimationAction resolveParadeAction() {
return AnimationAction.TWO_HAND_PARADE;
}
// ── Reaktionen anwenden ───────────────────────────────────────────────────
/**
* Normaler Treffer-Impact (Entität wurde getroffen, kein Block).
* Für Held: {@code PlayerInputControl} implementiert {@link AnimReceiver}.
* Für NPC: {@code WorldNpcsState.npcAnimReceiver(s)} liefert den Adapter.
*/
public static void applyNormalImpact(AnimReceiver receiver) {
receiver.receiveBlockingAnim(resolveIdleImpactAction(), IMPACT_SPEED_NORMAL, null);
}
/**
* Konterparade-Impact (Angreifer wird durch Parade gestoppt – Slow Motion).
* Wird aufgerufen auf dem ANGREIFER, nicht auf dem Parierenden.
*/
public static void applyParriedImpact(AnimReceiver receiver) {
receiver.receiveBlockingAnim(resolveIdleImpactAction(), IMPACT_SPEED_PARRIED, null);
}
}

View File

@@ -34,6 +34,7 @@ import de.blight.game.combat.CombatStyleBase;
import de.blight.game.combat.MeleeAttackCallback; import de.blight.game.combat.MeleeAttackCallback;
import de.blight.game.combat.WeaponCombatState; import de.blight.game.combat.WeaponCombatState;
import de.blight.game.combat.swordshield.SwordShieldCombat; import de.blight.game.combat.swordshield.SwordShieldCombat;
import de.blight.game.combat.twohanded.TwoHandedCombat;
import de.blight.game.navigation.CharacterNavigator; import de.blight.game.navigation.CharacterNavigator;
import de.blight.game.state.TerrainChunkState; import de.blight.game.state.TerrainChunkState;
@@ -1254,10 +1255,11 @@ public class PlayerInputControl implements CombatStyleBase.AnimReceiver {
} else if (blockPhase == BlockPhase.HOLDING && !combatAnimActive) { } else if (blockPhase == BlockPhase.HOLDING && !combatAnimActive) {
// Beim Blocken: Bewegungsanimation nach Taste, Strafe hat Vorrang // Beim Blocken: Bewegungsanimation nach Taste, Strafe hat Vorrang
AnimationAction blockMoveAnim; AnimationAction blockMoveAnim;
if (right) blockMoveAnim = AnimationAction.SWORD_BLOCK_STRAFE_RIGHT; boolean twoHand = drawnWeaponType == ItemSubCategory.TWO_HANDED_SWORD;
else if (left) blockMoveAnim = AnimationAction.SWORD_BLOCK_STRAFE_LEFT; if (right) blockMoveAnim = twoHand ? AnimationAction.TWO_HAND_BLOCK_STRAFE_RIGHT : AnimationAction.SWORD_BLOCK_STRAFE_RIGHT;
else if (forward) blockMoveAnim = AnimationAction.SWORD_BLOCK_MOVE; else if (left) blockMoveAnim = twoHand ? AnimationAction.TWO_HAND_BLOCK_STRAFE_LEFT : AnimationAction.SWORD_BLOCK_STRAFE_LEFT;
else if (backward) blockMoveAnim = AnimationAction.SWORD_BLOCK_MOVE_BACKWARD; else if (forward) blockMoveAnim = twoHand ? AnimationAction.TWO_HAND_BLOCK_MOVE : AnimationAction.SWORD_BLOCK_MOVE;
else if (backward) blockMoveAnim = twoHand ? AnimationAction.TWO_HAND_BLOCK_MOVE_BACKWARD : AnimationAction.SWORD_BLOCK_MOVE_BACKWARD;
else blockMoveAnim = getMeleeBlockAction(drawnWeaponType); else blockMoveAnim = getMeleeBlockAction(drawnWeaponType);
if (blockMoveAnim != currentAnim) { if (blockMoveAnim != currentAnim) {
@@ -1861,10 +1863,12 @@ public class PlayerInputControl implements CombatStyleBase.AnimReceiver {
} }
public static AnimationAction getMeleeBlockImpactAction(ItemSubCategory s) { public static AnimationAction getMeleeBlockImpactAction(ItemSubCategory s) {
if (s == ItemSubCategory.TWO_HANDED_SWORD) { return TwoHandedCombat.resolveBlockImpactAction(); }
return SwordShieldCombat.resolveBlockImpactAction(s); return SwordShieldCombat.resolveBlockImpactAction(s);
} }
public static AnimationAction getMeleeIdleImpactAction(ItemSubCategory s) { public static AnimationAction getMeleeIdleImpactAction(ItemSubCategory s) {
if (s == ItemSubCategory.TWO_HANDED_SWORD) { return TwoHandedCombat.resolveIdleImpactAction(); }
return SwordShieldCombat.resolveIdleImpactAction(s); return SwordShieldCombat.resolveIdleImpactAction(s);
} }
@@ -2143,7 +2147,9 @@ public class PlayerInputControl implements CombatStyleBase.AnimReceiver {
SWORD_WALK, AXE_WALK, TWO_HAND_WALK, SWORD_WALK, AXE_WALK, TWO_HAND_WALK,
BOW_WALK, CROSSBOW_WALK, CRYSTAL_WALK, BOW_WALK, CROSSBOW_WALK, CRYSTAL_WALK,
SWORD_BLOCK_MOVE, SWORD_BLOCK_MOVE_BACKWARD, SWORD_BLOCK_MOVE, SWORD_BLOCK_MOVE_BACKWARD,
SWORD_BLOCK_STRAFE_LEFT, SWORD_BLOCK_STRAFE_RIGHT -> "walking"; SWORD_BLOCK_STRAFE_LEFT, SWORD_BLOCK_STRAFE_RIGHT,
TWO_HAND_BLOCK_MOVE, TWO_HAND_BLOCK_MOVE_BACKWARD,
TWO_HAND_BLOCK_STRAFE_LEFT, TWO_HAND_BLOCK_STRAFE_RIGHT -> "walking";
case RUN, case RUN,
SWORD_RUN, AXE_RUN, TWO_HAND_RUN, SWORD_RUN, AXE_RUN, TWO_HAND_RUN,
BOW_RUN, CROSSBOW_RUN, CRYSTAL_RUN -> "running"; BOW_RUN, CROSSBOW_RUN, CRYSTAL_RUN -> "running";

View File

@@ -55,6 +55,7 @@ import de.blight.game.combat.DamageCalculator;
import de.blight.game.combat.MeleeAttackCallback; import de.blight.game.combat.MeleeAttackCallback;
import de.blight.game.combat.WeaponCombatState; import de.blight.game.combat.WeaponCombatState;
import de.blight.game.combat.swordshield.SwordShieldCombat; import de.blight.game.combat.swordshield.SwordShieldCombat;
import de.blight.game.combat.twohanded.TwoHandedCombat;
import de.blight.game.npc.AggroEvent; import de.blight.game.npc.AggroEvent;
import de.blight.game.npc.NpcAggroBehavior; import de.blight.game.npc.NpcAggroBehavior;
import de.blight.lang.TextResolver; import de.blight.lang.TextResolver;
@@ -2036,7 +2037,12 @@ public class WorldNpcsState extends BaseAppState implements MeleeAttackCallback
s.combat.phase = WeaponCombatState.Phase.COUNTER_WINDOW; s.combat.phase = WeaponCombatState.Phase.COUNTER_WINDOW;
s.combat.timer = dur * 2f; s.combat.timer = dur * 2f;
s.paradeFromParry = true; s.paradeFromParry = true;
SwordShieldCombat.applyParriedImpact(playerInput, playerInput.getDrawnWeaponType()); ItemSubCategory pwt = playerInput.getDrawnWeaponType();
if (pwt == ItemSubCategory.TWO_HANDED_SWORD) {
TwoHandedCombat.applyParriedImpact(playerInput);
} else {
SwordShieldCombat.applyParriedImpact(playerInput, pwt);
}
return; return;
} }
s.konterParadeAttackId = -1; // Entscheidung war kein Konter → zurücksetzen s.konterParadeAttackId = -1; // Entscheidung war kein Konter → zurücksetzen
@@ -2501,11 +2507,19 @@ public class WorldNpcsState extends BaseAppState implements MeleeAttackCallback
} }
private void staggerNpc(SpawnedNpc s) { private void staggerNpc(SpawnedNpc s) {
SwordShieldCombat.applyNormalImpact(npcAnimReceiver(s), s.combatWeaponType); if (s.combatWeaponType == ItemSubCategory.TWO_HANDED_SWORD) {
TwoHandedCombat.applyNormalImpact(npcAnimReceiver(s));
} else {
SwordShieldCombat.applyNormalImpact(npcAnimReceiver(s), s.combatWeaponType);
}
} }
private void staggerNpcSlow(SpawnedNpc s) { private void staggerNpcSlow(SpawnedNpc s) {
SwordShieldCombat.applyParriedImpact(npcAnimReceiver(s), s.combatWeaponType); if (s.combatWeaponType == ItemSubCategory.TWO_HANDED_SWORD) {
TwoHandedCombat.applyParriedImpact(npcAnimReceiver(s));
} else {
SwordShieldCombat.applyParriedImpact(npcAnimReceiver(s), s.combatWeaponType);
}
} }
private void setNpcCurrentActionSpeed(SpawnedNpc s, float speed) { private void setNpcCurrentActionSpeed(SpawnedNpc s, float speed) {
@@ -2575,15 +2589,18 @@ public class WorldNpcsState extends BaseAppState implements MeleeAttackCallback
s.paradeFromParry, playerInput.isBlocking()); s.paradeFromParry, playerInput.isBlocking());
if (isHeavy) { if (isHeavy) {
ItemSubCategory pwtH = playerInput.getDrawnWeaponType();
if (playerInput.isBlocking()) { if (playerInput.isBlocking()) {
// Schwerer Angriff bricht Block — kein HP-Verlust, aber Stagger // Schwerer Angriff bricht Block — kein HP-Verlust, aber Stagger
if (weaponSoundSystem != null) { weaponSoundSystem.play("impact"); } if (weaponSoundSystem != null) { weaponSoundSystem.play("impact"); }
playerInput.triggerBlockImpact(); playerInput.triggerBlockImpact();
SwordShieldCombat.applyNormalImpact(playerInput, playerInput.getDrawnWeaponType()); if (pwtH == ItemSubCategory.TWO_HANDED_SWORD) { TwoHandedCombat.applyNormalImpact(playerInput); }
else { SwordShieldCombat.applyNormalImpact(playerInput, pwtH); }
} else { } else {
if (weaponSoundSystem != null) { weaponSoundSystem.play("impact"); } if (weaponSoundSystem != null) { weaponSoundSystem.play("impact"); }
applyPlayerDamage(s, Math.max(1, base * 3)); applyPlayerDamage(s, Math.max(1, base * 3));
SwordShieldCombat.applyNormalImpact(playerInput, playerInput.getDrawnWeaponType()); if (pwtH == ItemSubCategory.TWO_HANDED_SWORD) { TwoHandedCombat.applyNormalImpact(playerInput); }
else { SwordShieldCombat.applyNormalImpact(playerInput, pwtH); }
} }
return; return;
} }
@@ -2600,7 +2617,9 @@ public class WorldNpcsState extends BaseAppState implements MeleeAttackCallback
// Konterparade: Spieler wurde bereits per applyParriedImpact gestaggert → keine zweite Animation. // Konterparade: Spieler wurde bereits per applyParriedImpact gestaggert → keine zweite Animation.
// Block+Konter: Spieler war NICHT gestaggert → normale Impact-Animation abspielen. // Block+Konter: Spieler war NICHT gestaggert → normale Impact-Animation abspielen.
if (!s.paradeFromParry) { if (!s.paradeFromParry) {
SwordShieldCombat.applyNormalImpact(playerInput, playerInput.getDrawnWeaponType()); ItemSubCategory pwtN = playerInput.getDrawnWeaponType();
if (pwtN == ItemSubCategory.TWO_HANDED_SWORD) { TwoHandedCombat.applyNormalImpact(playerInput); }
else { SwordShieldCombat.applyNormalImpact(playerInput, pwtN); }
} }
s.paradeFromParry = false; s.paradeFromParry = false;
} }
@@ -2686,14 +2705,17 @@ public class WorldNpcsState extends BaseAppState implements MeleeAttackCallback
} }
private AnimationAction resolveNpcBlockImpactAction(SpawnedNpc s) { private AnimationAction resolveNpcBlockImpactAction(SpawnedNpc s) {
if (s.combatWeaponType == ItemSubCategory.TWO_HANDED_SWORD) { return TwoHandedCombat.resolveBlockImpactAction(); }
return SwordShieldCombat.resolveBlockImpactAction(s.combatWeaponType); return SwordShieldCombat.resolveBlockImpactAction(s.combatWeaponType);
} }
private AnimationAction resolveNpcParadeAction(SpawnedNpc s) { private AnimationAction resolveNpcParadeAction(SpawnedNpc s) {
if (s.combatWeaponType == ItemSubCategory.TWO_HANDED_SWORD) { return TwoHandedCombat.resolveParadeAction(); }
return SwordShieldCombat.resolveParadeAction(s.combatWeaponType); return SwordShieldCombat.resolveParadeAction(s.combatWeaponType);
} }
private AnimationAction resolveNpcStaggerAction(SpawnedNpc s) { private AnimationAction resolveNpcStaggerAction(SpawnedNpc s) {
if (s.combatWeaponType == ItemSubCategory.TWO_HANDED_SWORD) { return TwoHandedCombat.resolveIdleImpactAction(); }
return SwordShieldCombat.resolveIdleImpactAction(s.combatWeaponType); return SwordShieldCombat.resolveIdleImpactAction(s.combatWeaponType);
} }