Impact System ausgearbeitet
This commit is contained in:
BIN
assets/imported/models/wooden straw man 3d model.glb
Normal file
BIN
assets/imported/models/wooden straw man 3d model.glb
Normal file
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 7.7 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 7.7 KiB |
BIN
blight-assets/src/main/resources/Models/imported/kampfdummy.j3o
Normal file
BIN
blight-assets/src/main/resources/Models/imported/kampfdummy.j3o
Normal file
Binary file not shown.
@@ -0,0 +1,30 @@
|
||||
#Tue Sep 22 20:54:03 CEST 2026
|
||||
attachedEmitters.count=0
|
||||
attachedLights.count=0
|
||||
attachedSounds.count=0
|
||||
castShadow=true
|
||||
category=
|
||||
cullDistance=120.0
|
||||
footstepSurface=
|
||||
interactableOffsetX=0.0
|
||||
interactableOffsetY=0.5
|
||||
interactableOffsetZ=0.0
|
||||
interactableRotY=0.0
|
||||
interactableType=NONE
|
||||
lod1Distance=30.0
|
||||
lod1Path=
|
||||
lod2Distance=80.0
|
||||
lod2Path=
|
||||
modelCategory=DUMMY
|
||||
name=kampfdummy
|
||||
pivotOffsetY=0.0
|
||||
placementOffsetY=0.0
|
||||
randomScaleMax=1.0
|
||||
randomScaleMin=1.0
|
||||
receiveShadow=true
|
||||
scaleX=1.0
|
||||
scaleY=1.0
|
||||
scaleZ=1.0
|
||||
solid=true
|
||||
tags=
|
||||
uniformScale=true
|
||||
@@ -0,0 +1,30 @@
|
||||
#Tue Sep 22 20:52:39 CEST 2026
|
||||
attachedEmitters.count=0
|
||||
attachedLights.count=0
|
||||
attachedSounds.count=0
|
||||
castShadow=true
|
||||
category=
|
||||
cullDistance=120.0
|
||||
footstepSurface=
|
||||
interactableOffsetX=0.0
|
||||
interactableOffsetY=0.5
|
||||
interactableOffsetZ=0.0
|
||||
interactableRotY=0.0
|
||||
interactableType=NONE
|
||||
lod1Distance=30.0
|
||||
lod1Path=
|
||||
lod2Distance=80.0
|
||||
lod2Path=
|
||||
modelCategory=NONE
|
||||
name=wooden straw man 3d model
|
||||
pivotOffsetY=0.0
|
||||
placementOffsetY=0.0
|
||||
randomScaleMax=1.0
|
||||
randomScaleMin=1.0
|
||||
receiveShadow=true
|
||||
scaleX=1.0
|
||||
scaleY=1.0
|
||||
scaleZ=1.0
|
||||
solid=true
|
||||
tags=
|
||||
uniformScale=true
|
||||
@@ -3,16 +3,19 @@ package de.blight.game.combat;
|
||||
import com.jme3.math.Vector3f;
|
||||
|
||||
/**
|
||||
* Wird aufgerufen wenn der Spieler einen Nahkampfangriff auslöst.
|
||||
* Implementierende Klassen können z.B. Treffererkennung auf Zielobjekten durchführen.
|
||||
* Wird pro Frame während des Treffer-Fensters einer Nahkampf-Animation aufgerufen.
|
||||
* Jeder Schwung hat eine eindeutige attackId; hitThisAttack muss pro ID zurückgesetzt werden.
|
||||
*/
|
||||
public interface MeleeAttackCallback {
|
||||
|
||||
/**
|
||||
* @param charPosition Weltposition des Charakters beim Angriff
|
||||
* @param charForward Blickrichtung des Charakters (normiert, Y=0)
|
||||
* @param baseDamage Basisschaden der Waffe (0 = unbewaffnet)
|
||||
* @param critMultiplier Schadens-Multiplikator (1.0 = normal, 2.0 = Kombo-5, 1.5–3.0 = Schwerer Angriff)
|
||||
* @param attackId Eindeutige ID pro Schwung (inkrementiert bei jedem Angriff)
|
||||
* @param gripPos Weltposition des Griffes (Hand-Bone)
|
||||
* @param bladeDir Normierter Klingen-Richtungsvektor in Weltkoordinaten
|
||||
* @param bladeLength Länge der Klinge in Metern (Ray-Reichweite)
|
||||
* @param baseDamage Basisschaden der Waffe (0 = unbewaffnet → Fallback auf 5)
|
||||
* @param critMultiplier Schadens-Multiplikator (1.0 = normal, 2.0 = Kombo-5, 3.0 = Schwerer Angriff)
|
||||
*/
|
||||
void onMeleeAttack(Vector3f charPosition, Vector3f charForward, int baseDamage, float critMultiplier);
|
||||
void onMeleeAttack(int attackId, Vector3f gripPos, Vector3f bladeDir,
|
||||
float bladeLength, int baseDamage, float critMultiplier);
|
||||
}
|
||||
|
||||
@@ -131,7 +131,15 @@ public class PlayerInputControl {
|
||||
private float heavyLungeEndRemaining = 0f;
|
||||
/** Gesamtdauer der aktuellen Kampfanim — für Kombo-Fenster-Timing (öffnet bei 50%). */
|
||||
private float combatAnimDuration = 0f;
|
||||
private MeleeAttackCallback meleeAttackCallback = null;
|
||||
private MeleeAttackCallback meleeAttackCallback = null;
|
||||
private WeaponAttachmentControl weaponAttach = null;
|
||||
private static final float HIT_WINDOW_START = 0.20f;
|
||||
private static final float HIT_WINDOW_END = 0.75f;
|
||||
private static final float WEAPON_BLADE_LENGTH = 1.0f;
|
||||
private int attackIdCounter = 0;
|
||||
private int currentAttackId = -1;
|
||||
private boolean hitWindowActive = false;
|
||||
private float hitWindowCritMult = 1.0f;
|
||||
// Draw/Sheath Animation System
|
||||
private boolean drawAnimActive = false;
|
||||
private float drawAnimTimer = 0f;
|
||||
@@ -372,7 +380,8 @@ public class PlayerInputControl {
|
||||
public ItemSubCategory getDrawnWeaponType() { return drawnWeaponType; }
|
||||
public boolean isLastAttackCritical() { return lastAttackCritical; }
|
||||
public com.jme3.anim.SkinningControl getSkinningControl() { return skinningControl; }
|
||||
public void setMeleeAttackCallback(MeleeAttackCallback cb) { this.meleeAttackCallback = cb; }
|
||||
public void setMeleeAttackCallback(MeleeAttackCallback cb) { this.meleeAttackCallback = cb; }
|
||||
public void setWeaponAttachmentControl(WeaponAttachmentControl w) { this.weaponAttach = w; }
|
||||
|
||||
public boolean isPaused() { return paused; }
|
||||
|
||||
@@ -833,6 +842,7 @@ public class PlayerInputControl {
|
||||
physicsChar.setWalkDirection(inLungeWindow ? heavyLungeVel : Vector3f.ZERO);
|
||||
if (combatAnimRemaining <= 0f) {
|
||||
combatAnimActive = false;
|
||||
hitWindowActive = false;
|
||||
heavyLungeVel = null;
|
||||
heavyLungeStartRemaining = 0f;
|
||||
heavyLungeEndRemaining = 0f;
|
||||
@@ -856,6 +866,15 @@ public class PlayerInputControl {
|
||||
comboWindowActive = true;
|
||||
log.info("[Combat] Kombo-Fenster geöffnet (step={}, remaining={}s)", comboStep, combatAnimRemaining);
|
||||
}
|
||||
// Per-Frame Waffen-Treffercheck während Hit-Window
|
||||
if (hitWindowActive && isMeleeType(drawnWeaponType) && combatAnimDuration > 0f) {
|
||||
float t = (combatAnimDuration - combatAnimRemaining) / combatAnimDuration;
|
||||
if (t >= HIT_WINDOW_START && t <= HIT_WINDOW_END) {
|
||||
fireWeaponCallback(hitWindowCritMult);
|
||||
} else if (t > HIT_WINDOW_END) {
|
||||
hitWindowActive = false;
|
||||
}
|
||||
}
|
||||
pollFootsteps(tpf);
|
||||
return;
|
||||
}
|
||||
@@ -989,8 +1008,10 @@ public class PlayerInputControl {
|
||||
int step = comboStep;
|
||||
boolean isComboFollow = comboWindowActive; // war Kombo-Fenster offen?
|
||||
AnimationAction atk = getMeleeComboAction(drawnWeaponType, step);
|
||||
lastAttackCritical = (step == 4);
|
||||
fireMeleeCallback(lastAttackCritical ? 2.0f : 1.0f);
|
||||
lastAttackCritical = (step == 4);
|
||||
currentAttackId = ++attackIdCounter;
|
||||
hitWindowActive = true;
|
||||
hitWindowCritMult = lastAttackCritical ? 2.0f : 1.0f;
|
||||
comboStep = (step + 1) % 5;
|
||||
comboWindowActive = false;
|
||||
float dur = resolveClipLength(atk, 0.6f);
|
||||
@@ -1302,21 +1323,20 @@ public class PlayerInputControl {
|
||||
};
|
||||
}
|
||||
|
||||
private void fireMeleeCallback(float critMultiplier) {
|
||||
if (meleeAttackCallback == null || physicsChar == null) {
|
||||
private void fireWeaponCallback(float critMultiplier) {
|
||||
if (meleeAttackCallback == null || weaponAttach == null) {
|
||||
return;
|
||||
}
|
||||
com.jme3.scene.Spatial ws = weaponAttach.getWeaponSpatial();
|
||||
com.jme3.scene.Node hn = weaponAttach.getHandNode();
|
||||
if (ws == null || hn == null) {
|
||||
return;
|
||||
}
|
||||
int base = (mainCharacter != null && mainCharacter.getEquippedMeleeWeapon() != null)
|
||||
? mainCharacter.getEquippedMeleeWeapon().getDamage() : 0;
|
||||
Vector3f pos = physicsChar.getPhysicsLocation();
|
||||
Vector3f fwd = (visual != null)
|
||||
? visual.getLocalRotation().mult(new Vector3f(0f, 0f, -1f))
|
||||
: new Vector3f(0f, 0f, -1f);
|
||||
fwd.y = 0f;
|
||||
if (fwd.lengthSquared() > 0.001f) {
|
||||
fwd.normalizeLocal();
|
||||
}
|
||||
meleeAttackCallback.onMeleeAttack(pos, fwd, base, critMultiplier);
|
||||
Vector3f grip = hn.getWorldTranslation().clone();
|
||||
Vector3f bladeDir = ws.getWorldRotation().mult(com.jme3.math.Vector3f.UNIT_Y).normalizeLocal();
|
||||
meleeAttackCallback.onMeleeAttack(currentAttackId, grip, bladeDir, WEAPON_BLADE_LENGTH, base, critMultiplier);
|
||||
}
|
||||
|
||||
private float computeHeavyMultiplier() {
|
||||
@@ -1368,8 +1388,9 @@ public class PlayerInputControl {
|
||||
if (!weaponDrawn || drawnWeaponType == null || !isMeleeType(drawnWeaponType)) {
|
||||
return;
|
||||
}
|
||||
currentAttackId = ++attackIdCounter;
|
||||
float mult = computeHeavyMultiplier();
|
||||
fireMeleeCallback(mult);
|
||||
fireWeaponCallback(mult);
|
||||
log.info("[Combat] Schwerer Angriff ausgelöst | Haltezeit={}s Multiplikator={}x",
|
||||
String.format("%.2f", attackHoldTimer), String.format("%.2f", mult));
|
||||
}
|
||||
|
||||
@@ -84,6 +84,9 @@ public class WeaponAttachmentControl extends AbstractControl {
|
||||
this.mc = mc;
|
||||
}
|
||||
|
||||
public Spatial getWeaponSpatial() { return weaponSpatial; }
|
||||
public Node getHandNode() { return handNode; }
|
||||
|
||||
@Override
|
||||
protected void controlUpdate(float tpf) {
|
||||
if (skinCtrl == null) {
|
||||
|
||||
@@ -362,7 +362,9 @@ public class WorldScene extends BaseAppState {
|
||||
MainCharacter mc = findMainCharacter();
|
||||
if (mc != null) {
|
||||
playerInput.setMainCharacter(mc);
|
||||
character.addControl(new de.blight.game.control.WeaponAttachmentControl(assetManager, playerInput, mc));
|
||||
var wac = new de.blight.game.control.WeaponAttachmentControl(assetManager, playerInput, mc);
|
||||
character.addControl(wac);
|
||||
playerInput.setWeaponAttachmentControl(wac);
|
||||
// SaveGameState mit Charakter und Positions-Lieferant verknüpfen
|
||||
de.blight.game.state.SaveGameState saveState =
|
||||
app.getStateManager().getState(de.blight.game.state.SaveGameState.class);
|
||||
|
||||
@@ -4,7 +4,10 @@ import com.jme3.app.Application;
|
||||
import com.jme3.app.SimpleApplication;
|
||||
import com.jme3.app.state.BaseAppState;
|
||||
import com.jme3.asset.AssetManager;
|
||||
import com.jme3.bullet.BulletAppState;
|
||||
import com.jme3.bullet.control.CharacterControl;
|
||||
import com.jme3.bullet.control.RigidBodyControl;
|
||||
import com.jme3.bullet.util.CollisionShapeFactory;
|
||||
import com.jme3.font.BitmapFont;
|
||||
import com.jme3.font.BitmapText;
|
||||
import com.jme3.input.InputManager;
|
||||
@@ -44,7 +47,9 @@ import org.slf4j.LoggerFactory;
|
||||
import java.io.IOException;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Verwaltet Kampfpuppen auf der Karte: HP-Tracking, Treffer-Erkennung,
|
||||
@@ -55,8 +60,6 @@ public class WorldDummyState extends BaseAppState implements MeleeAttackCallback
|
||||
private static final Logger log = LoggerFactory.getLogger(WorldDummyState.class);
|
||||
|
||||
// ── Spielwelt-Konstanten ──────────────────────────────────────────────────
|
||||
private static final float ATTACK_RANGE = 2.5f;
|
||||
private static final float ATTACK_CONE_DOT = 0.2f;
|
||||
private static final float FOCUS_RANGE = 6f;
|
||||
private static final float RESTORE_DELAY = 60f;
|
||||
private static final float HP_BAR_SHOW_TIME = 5f;
|
||||
@@ -82,14 +85,17 @@ public class WorldDummyState extends BaseAppState implements MeleeAttackCallback
|
||||
private final PlayerInputControl playerInput;
|
||||
private final KeyBindings keyBindings;
|
||||
|
||||
private AssetManager assetManager;
|
||||
private InputManager inputManager;
|
||||
private Node rootNode;
|
||||
private Node guiNode;
|
||||
private Camera cam;
|
||||
private AssetManager assetManager;
|
||||
private InputManager inputManager;
|
||||
private Node rootNode;
|
||||
private Node guiNode;
|
||||
private Camera cam;
|
||||
private BulletAppState bulletAppState;
|
||||
|
||||
// ── Laufzeit-Zustand ─────────────────────────────────────────────────────
|
||||
private final List<DummyRuntime> dummies = new ArrayList<>();
|
||||
private final List<DummyRuntime> dummies = new ArrayList<>();
|
||||
private int lastAttackId = -1;
|
||||
private final Set<String> hitThisAttack = new HashSet<>();
|
||||
private int hoveredDummyIdx = -1; // gefallene Puppe in Aufstell-Reichweite
|
||||
private int focusedDummyIdx = -1; // nächste Puppe in Fokus-Reichweite (Waffe gezückt)
|
||||
|
||||
@@ -116,11 +122,12 @@ public class WorldDummyState extends BaseAppState implements MeleeAttackCallback
|
||||
@Override
|
||||
protected void initialize(Application app) {
|
||||
SimpleApplication sapp = (SimpleApplication) app;
|
||||
this.assetManager = app.getAssetManager();
|
||||
this.inputManager = app.getInputManager();
|
||||
this.rootNode = sapp.getRootNode();
|
||||
this.guiNode = sapp.getGuiNode();
|
||||
this.cam = app.getCamera();
|
||||
this.assetManager = app.getAssetManager();
|
||||
this.inputManager = app.getInputManager();
|
||||
this.rootNode = sapp.getRootNode();
|
||||
this.guiNode = sapp.getGuiNode();
|
||||
this.cam = app.getCamera();
|
||||
this.bulletAppState = app.getStateManager().getState(BulletAppState.class);
|
||||
|
||||
loadDummies();
|
||||
buildEnemyHud();
|
||||
@@ -149,6 +156,9 @@ public class WorldDummyState extends BaseAppState implements MeleeAttackCallback
|
||||
@Override
|
||||
protected void cleanup(Application app) {
|
||||
for (DummyRuntime dr : dummies) {
|
||||
if (dr.rbc != null && bulletAppState != null) {
|
||||
bulletAppState.getPhysicsSpace().remove(dr.rbc);
|
||||
}
|
||||
rootNode.detachChild(dr.dummyNode);
|
||||
}
|
||||
dummies.clear();
|
||||
@@ -203,10 +213,22 @@ public class WorldDummyState extends BaseAppState implements MeleeAttackCallback
|
||||
dummyNode.setLocalScale(m.scale());
|
||||
rootNode.attachChild(dummyNode);
|
||||
|
||||
RigidBodyControl rbc = null;
|
||||
if (m.solid() && bulletAppState != null) {
|
||||
try {
|
||||
rbc = new RigidBodyControl(CollisionShapeFactory.createMeshShape(model), 0f);
|
||||
model.addControl(rbc);
|
||||
bulletAppState.getPhysicsSpace().add(rbc);
|
||||
} catch (Exception pe) {
|
||||
log.warn("[WorldDummy] Kollisions-Shape für '{}' fehlgeschlagen: {}", m.modelPath(), pe.getMessage());
|
||||
rbc = null;
|
||||
}
|
||||
}
|
||||
|
||||
Geometry hpFg = (Geometry) ((Node) hpBarNode.getChild(0)).getChild(1);
|
||||
return new DummyRuntime(
|
||||
m.interactableId() != null ? m.interactableId() : "?",
|
||||
m.x(), m.y(), m.z(), dummyNode, hpBarNode, hpFg, maxHp);
|
||||
m.x(), m.y(), m.z(), dummyNode, hpBarNode, hpFg, maxHp, rbc);
|
||||
}
|
||||
|
||||
private Spatial loadModel(PlacedModel m) {
|
||||
@@ -374,25 +396,24 @@ public class WorldDummyState extends BaseAppState implements MeleeAttackCallback
|
||||
// ── MeleeAttackCallback ───────────────────────────────────────────────────
|
||||
|
||||
@Override
|
||||
public void onMeleeAttack(Vector3f charPos, Vector3f charForward, int baseDamage, float critMultiplier) {
|
||||
public void onMeleeAttack(int attackId, Vector3f gripPos, Vector3f bladeDir,
|
||||
float bladeLength, int baseDamage, float critMultiplier) {
|
||||
if (attackId != lastAttackId) {
|
||||
lastAttackId = attackId;
|
||||
hitThisAttack.clear();
|
||||
}
|
||||
for (DummyRuntime dr : dummies) {
|
||||
if (dr.fallen) {
|
||||
if (dr.fallen || hitThisAttack.contains(dr.id)) {
|
||||
continue;
|
||||
}
|
||||
float dx = dr.worldX - charPos.x;
|
||||
float dz = dr.worldZ - charPos.z;
|
||||
float distSq = dx * dx + dz * dz;
|
||||
if (distSq > ATTACK_RANGE * ATTACK_RANGE) {
|
||||
continue;
|
||||
// Distanz vom Griff zum Dummy-Zentrum (Torso-Höhe ~0.9m)
|
||||
float dx = dr.worldX - gripPos.x;
|
||||
float dy = (dr.worldY + 0.9f) - gripPos.y;
|
||||
float dz = dr.worldZ - gripPos.z;
|
||||
if (dx * dx + dy * dy + dz * dz <= bladeLength * bladeLength) {
|
||||
hitThisAttack.add(dr.id);
|
||||
applyDamage(dr, DamageCalculator.calculate(baseDamage, critMultiplier));
|
||||
}
|
||||
float len = (float) Math.sqrt(distSq);
|
||||
if (len > 0.05f) {
|
||||
float dot = (dx / len) * charForward.x + (dz / len) * charForward.z;
|
||||
if (dot < ATTACK_CONE_DOT) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
applyDamage(dr, DamageCalculator.calculate(baseDamage, critMultiplier));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -401,8 +422,6 @@ public class WorldDummyState extends BaseAppState implements MeleeAttackCallback
|
||||
private void applyDamage(DummyRuntime dr, int damage) {
|
||||
dr.currentHp = Math.max(0, dr.currentHp - damage);
|
||||
dr.restoreTimer = 0f;
|
||||
dr.hpBarTimer = HP_BAR_SHOW_TIME;
|
||||
dr.hpBarNode.setCullHint(Spatial.CullHint.Inherit);
|
||||
refreshWorldHpBar(dr);
|
||||
log.info("[Dummy {}] -{} HP → {}/{}{}", dr.id, damage, dr.currentHp, dr.maxHp,
|
||||
dr.currentHp <= 0 ? " K.O.!" : "");
|
||||
@@ -551,20 +570,22 @@ public class WorldDummyState extends BaseAppState implements MeleeAttackCallback
|
||||
// ── Innere Klasse ─────────────────────────────────────────────────────────
|
||||
|
||||
private static class DummyRuntime {
|
||||
final String id;
|
||||
final float worldX, worldY, worldZ;
|
||||
final Node dummyNode;
|
||||
final Node hpBarNode;
|
||||
final Geometry hpBarFg;
|
||||
final String id;
|
||||
final float worldX, worldY, worldZ;
|
||||
final Node dummyNode;
|
||||
final Node hpBarNode;
|
||||
final Geometry hpBarFg;
|
||||
final RigidBodyControl rbc;
|
||||
int currentHp;
|
||||
final int maxHp;
|
||||
final int maxHp;
|
||||
float restoreTimer = 0f;
|
||||
float hpBarTimer = 0f;
|
||||
boolean fallen = false;
|
||||
float fallenLerp = 0f;
|
||||
|
||||
DummyRuntime(String id, float wx, float wy, float wz,
|
||||
Node dummyNode, Node hpBarNode, Geometry hpBarFg, int maxHp) {
|
||||
Node dummyNode, Node hpBarNode, Geometry hpBarFg, int maxHp,
|
||||
RigidBodyControl rbc) {
|
||||
this.id = id;
|
||||
this.worldX = wx;
|
||||
this.worldY = wy;
|
||||
@@ -574,6 +595,7 @@ public class WorldDummyState extends BaseAppState implements MeleeAttackCallback
|
||||
this.hpBarFg = hpBarFg;
|
||||
this.maxHp = maxHp;
|
||||
this.currentHp = maxHp;
|
||||
this.rbc = rbc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,3 +14,4 @@ Models/northcoast/alte_fischerhytte.j3o 322.73001 2.00000 -856.09003 -1.83155 1.
|
||||
Models/imported/feuerstelle1.j3o 1.46137 2.00000 -1.08840 0.00000 1.00000 0.00000 0.00000 true true true 30.00000 80.00000 120.00000 NONE
|
||||
Models/trees/palm/palm_20260914_195434.j3o 226.76038 1.25643 -893.77020 -1.59663 1.00000 -0.00000 0.00000 true true true 50.00000 150.00000 300.00000 PALM
|
||||
Models/trees/willow/willow_20260914_210538.j3o 256.04678 5.84959 -887.66675 0.00000 1.00000 0.00000 0.00000 true true true 50.00000 150.00000 300.00000 TREE
|
||||
Models/imported/kampfdummy.j3o -5.95449 2.00000 -11.12527 0.00000 1.00000 0.00000 0.00000 true true true 30.00000 80.00000 120.00000 DUMMY
|
||||
|
||||
Reference in New Issue
Block a user