Sounds für das Kampfsystem hinzugefügt
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
package de.blight.game.audio;
|
||||
|
||||
import com.jme3.asset.AssetManager;
|
||||
import com.jme3.audio.AudioData;
|
||||
import com.jme3.audio.AudioNode;
|
||||
import com.jme3.scene.Node;
|
||||
import de.blight.game.state.AudioSettingsState;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class WeaponSoundSystem {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(WeaponSoundSystem.class);
|
||||
|
||||
private static final float BASE_VOLUME = 0.85f;
|
||||
private static final float PITCH_CENTER = 1.00f;
|
||||
private static final float PITCH_RANGE = 0.08f;
|
||||
|
||||
private final Map<String, List<AudioNode>> pools = new HashMap<>();
|
||||
private final AudioSettingsState audioSettings;
|
||||
private final Random random = new Random();
|
||||
|
||||
public WeaponSoundSystem(AssetManager am, Node parentNode,
|
||||
AudioSettingsState audioSettings,
|
||||
Path assetRoot, String baseDir) {
|
||||
this.audioSettings = audioSettings;
|
||||
Path absBase = assetRoot.resolve(baseDir);
|
||||
if (!Files.isDirectory(absBase)) {
|
||||
log.warn("[WeaponSound] Basis-Verzeichnis nicht gefunden: {}", absBase);
|
||||
return;
|
||||
}
|
||||
try (var stream = Files.list(absBase)) {
|
||||
stream.filter(Files::isDirectory).sorted().forEach(dir -> {
|
||||
String category = dir.getFileName().toString();
|
||||
List<AudioNode> nodes = scanDir(am, parentNode, assetRoot,
|
||||
baseDir + category + "/");
|
||||
if (!nodes.isEmpty()) {
|
||||
pools.put(category, nodes);
|
||||
log.info("[WeaponSound] '{}': {} Sound(s) geladen", category, nodes.size());
|
||||
}
|
||||
});
|
||||
} catch (IOException e) {
|
||||
log.warn("[WeaponSound] Scan-Fehler {}: {}", absBase, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void play(String category) {
|
||||
List<AudioNode> nodes = pools.get(category);
|
||||
if (nodes == null || nodes.isEmpty()) return;
|
||||
float vol = BASE_VOLUME * (audioSettings != null ? audioSettings.effectiveEffects() : 1f);
|
||||
float pitch = PITCH_CENTER + (random.nextFloat() - 0.5f) * PITCH_RANGE;
|
||||
AudioNode node = nodes.get(random.nextInt(nodes.size()));
|
||||
node.setVolume(vol);
|
||||
node.setPitch(pitch);
|
||||
node.playInstance();
|
||||
}
|
||||
|
||||
private List<AudioNode> scanDir(AssetManager am, Node parentNode,
|
||||
Path assetRoot, String relDir) {
|
||||
Path absDir = assetRoot.resolve(relDir);
|
||||
if (!Files.isDirectory(absDir)) return Collections.emptyList();
|
||||
List<Path> files;
|
||||
try (var stream = Files.list(absDir)) {
|
||||
files = stream
|
||||
.filter(p -> Files.isRegularFile(p)
|
||||
&& p.getFileName().toString().toLowerCase().endsWith(".ogg"))
|
||||
.sorted()
|
||||
.collect(Collectors.toList());
|
||||
} catch (IOException e) {
|
||||
log.warn("[WeaponSound] Scan-Fehler {}: {}", absDir, e.getMessage());
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<AudioNode> nodes = new ArrayList<>(files.size());
|
||||
for (Path f : files) {
|
||||
String assetPath = relDir + f.getFileName().toString();
|
||||
try {
|
||||
AudioNode node = new AudioNode(am, assetPath, AudioData.DataType.Buffer);
|
||||
node.setPositional(false);
|
||||
node.setLooping(false);
|
||||
parentNode.attachChild(node);
|
||||
nodes.add(node);
|
||||
} catch (Exception e) {
|
||||
log.warn("[WeaponSound] Nicht ladbar '{}': {}", assetPath, e.getMessage());
|
||||
}
|
||||
}
|
||||
return nodes;
|
||||
}
|
||||
}
|
||||
@@ -140,6 +140,15 @@ public class PlayerInputControl {
|
||||
private int currentAttackId = -1;
|
||||
private boolean hitWindowActive = false;
|
||||
private float hitWindowCritMult = 1.0f;
|
||||
// Angriffs-Sound via Waffen-Geschwindigkeit
|
||||
private static final float ATTACK_SOUND_VELOCITY = 3.5f;
|
||||
// Attacken mit Backswing → Sound erst nach Mindest-Fortschritt erlauben
|
||||
private static final float ATTACK2_SOUND_MIN_PROGRESS = 0.35f;
|
||||
private static final float ATTACK5_SOUND_MIN_PROGRESS = 0.35f;
|
||||
private boolean attackSoundFired = false;
|
||||
private int currentComboStep = 0;
|
||||
private final Vector3f lastGripPos = new Vector3f();
|
||||
private final Vector3f scratchGripVel = new Vector3f();
|
||||
// Draw/Sheath Animation System
|
||||
private boolean drawAnimActive = false;
|
||||
private float drawAnimTimer = 0f;
|
||||
@@ -184,6 +193,7 @@ public class PlayerInputControl {
|
||||
public static final float WATER_WADING_DEPTH = 0.75f;
|
||||
|
||||
private de.blight.game.audio.FootstepSystem footstepSystem;
|
||||
private de.blight.game.audio.WeaponSoundSystem weaponSoundSystem;
|
||||
private java.util.function.BiFunction<Float, Float, float[]> surfaceQuery;
|
||||
private java.util.function.BiFunction<Float, Float, Float> waterDepthQuery;
|
||||
private com.jme3.audio.AudioNode wadingNode;
|
||||
@@ -866,7 +876,27 @@ 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
|
||||
// Per-Frame Waffen-Treffercheck + Geschwindigkeits-Sound
|
||||
if (isMeleeType(drawnWeaponType) && weaponAttach != null) {
|
||||
com.jme3.scene.Node hn = weaponAttach.getHandNode();
|
||||
if (hn != null && !attackSoundFired && weaponSoundSystem != null) {
|
||||
Vector3f grip = hn.getWorldTranslation();
|
||||
scratchGripVel.set(grip).subtractLocal(lastGripPos).divideLocal(tpf);
|
||||
float speed = scratchGripVel.length();
|
||||
float progress = combatAnimDuration > 0f
|
||||
? (combatAnimDuration - combatAnimRemaining) / combatAnimDuration : 0f;
|
||||
float minProg = switch (currentComboStep) {
|
||||
case 1 -> ATTACK2_SOUND_MIN_PROGRESS;
|
||||
case 4 -> ATTACK5_SOUND_MIN_PROGRESS;
|
||||
default -> 0f;
|
||||
};
|
||||
if (progress >= minProg && speed >= ATTACK_SOUND_VELOCITY) {
|
||||
attackSoundFired = true;
|
||||
weaponSoundSystem.play("attack");
|
||||
}
|
||||
lastGripPos.set(grip);
|
||||
}
|
||||
}
|
||||
if (hitWindowActive && isMeleeType(drawnWeaponType) && combatAnimDuration > 0f) {
|
||||
float t = (combatAnimDuration - combatAnimRemaining) / combatAnimDuration;
|
||||
if (t >= HIT_WINDOW_START && t <= HIT_WINDOW_END) {
|
||||
@@ -961,7 +991,10 @@ public class PlayerInputControl {
|
||||
if (!weaponDrawn) {
|
||||
boolean hasShield = mainCharacter.getEquippedShield() != null;
|
||||
startDrawAnimation(hasShield,
|
||||
() -> { weaponDrawn = true; drawnWeaponType = sub; currentAnim = null; },
|
||||
() -> {
|
||||
weaponDrawn = true; drawnWeaponType = sub; currentAnim = null;
|
||||
if (weaponSoundSystem != null) weaponSoundSystem.play("unsheath");
|
||||
},
|
||||
null);
|
||||
} else if (isMeleeType(drawnWeaponType)) {
|
||||
sheathCurrentWeapon();
|
||||
@@ -992,6 +1025,7 @@ public class PlayerInputControl {
|
||||
attackHoldPending = false;
|
||||
heavyLungeVel = null;
|
||||
blockPhase = BlockPhase.NONE;
|
||||
if (weaponSoundSystem != null) weaponSoundSystem.play("sheath");
|
||||
boolean hasShield = mainCharacter != null && mainCharacter.getEquippedShield() != null;
|
||||
startDrawAnimation(hasShield,
|
||||
() -> { weaponDrawn = false; drawnWeaponType = null; currentAnim = null; },
|
||||
@@ -1012,8 +1046,12 @@ public class PlayerInputControl {
|
||||
currentAttackId = ++attackIdCounter;
|
||||
hitWindowActive = true;
|
||||
hitWindowCritMult = lastAttackCritical ? 2.0f : 1.0f;
|
||||
currentComboStep = step;
|
||||
comboStep = (step + 1) % 5;
|
||||
comboWindowActive = false;
|
||||
attackSoundFired = false;
|
||||
if (weaponAttach != null && weaponAttach.getHandNode() != null)
|
||||
lastGripPos.set(weaponAttach.getHandNode().getWorldTranslation());
|
||||
float dur = resolveClipLength(atk, 0.6f);
|
||||
// Kombo-Folgeschlag: kurzer Blend für sauberen Übergang; erster Schlag: Hard-Cut
|
||||
nextTransitionLength = isComboFollow ? COMBAT_BLEND : -1.0;
|
||||
@@ -1388,7 +1426,11 @@ public class PlayerInputControl {
|
||||
if (!weaponDrawn || drawnWeaponType == null || !isMeleeType(drawnWeaponType)) {
|
||||
return;
|
||||
}
|
||||
currentAttackId = ++attackIdCounter;
|
||||
currentAttackId = ++attackIdCounter;
|
||||
currentComboStep = -1;
|
||||
attackSoundFired = false;
|
||||
if (weaponAttach != null && weaponAttach.getHandNode() != null)
|
||||
lastGripPos.set(weaponAttach.getHandNode().getWorldTranslation());
|
||||
float mult = computeHeavyMultiplier();
|
||||
fireWeaponCallback(mult);
|
||||
log.info("[Combat] Schwerer Angriff ausgelöst | Haltezeit={}s Multiplikator={}x",
|
||||
@@ -1437,6 +1479,10 @@ public class PlayerInputControl {
|
||||
this.surfaceQuery = query;
|
||||
}
|
||||
|
||||
public void setWeaponSoundSystem(de.blight.game.audio.WeaponSoundSystem wss) {
|
||||
this.weaponSoundSystem = wss;
|
||||
}
|
||||
|
||||
public void setWaterDepthQuery(java.util.function.BiFunction<Float, Float, Float> query) {
|
||||
this.waterDepthQuery = query;
|
||||
}
|
||||
|
||||
@@ -108,6 +108,7 @@ public class WorldScene extends BaseAppState {
|
||||
private de.blight.game.state.WaterfallState waterfallState;
|
||||
private de.blight.game.state.PlacedSoundSystem placedSoundSystem;
|
||||
private de.blight.game.audio.FootstepSystem footstepSystem;
|
||||
private de.blight.game.audio.WeaponSoundSystem weaponSoundSystem;
|
||||
private de.blight.game.state.DrownState drownState;
|
||||
private WaterInteractionState waterInteractionState;
|
||||
|
||||
@@ -319,6 +320,9 @@ public class WorldScene extends BaseAppState {
|
||||
footstepSystem = new de.blight.game.audio.FootstepSystem(assetManager, rootNode, audioSettings,
|
||||
AnimationLibrary.findAssetRoot());
|
||||
playerInput.setFootstepSystem(footstepSystem, this::querySurfaceWeights);
|
||||
weaponSoundSystem = new de.blight.game.audio.WeaponSoundSystem(assetManager, rootNode,
|
||||
audioSettings, AnimationLibrary.findAssetRoot(), "audio/weapons/sword/");
|
||||
playerInput.setWeaponSoundSystem(weaponSoundSystem);
|
||||
playerInput.setWaterDepthQuery(this::queryWaterDepth);
|
||||
try {
|
||||
com.jme3.audio.AudioNode wadingNode = new com.jme3.audio.AudioNode(
|
||||
@@ -376,8 +380,10 @@ public class WorldScene extends BaseAppState {
|
||||
new WorldItemsState(keyBindings, physicsChar, mc, playerInput));
|
||||
app.getStateManager().attach(
|
||||
new WorldInteractableState(keyBindings, physicsChar, playerInput));
|
||||
app.getStateManager().attach(
|
||||
new de.blight.game.state.WorldDummyState(physicsChar, playerInput, keyBindings));
|
||||
de.blight.game.state.WorldDummyState dummyStateRef =
|
||||
new de.blight.game.state.WorldDummyState(physicsChar, playerInput, keyBindings);
|
||||
dummyStateRef.setWeaponSoundSystem(weaponSoundSystem);
|
||||
app.getStateManager().attach(dummyStateRef);
|
||||
de.blight.common.MonologueRegistry.init(de.blight.common.MonologueIO.load());
|
||||
de.blight.game.state.DialogHudState dialogHud = new de.blight.game.state.DialogHudState();
|
||||
dialogHud.setMainCharacter(mc);
|
||||
|
||||
@@ -91,6 +91,7 @@ public class WorldDummyState extends BaseAppState implements MeleeAttackCallback
|
||||
private Node guiNode;
|
||||
private Camera cam;
|
||||
private BulletAppState bulletAppState;
|
||||
private de.blight.game.audio.WeaponSoundSystem weaponSoundSystem;
|
||||
|
||||
// ── Laufzeit-Zustand ─────────────────────────────────────────────────────
|
||||
private final List<DummyRuntime> dummies = new ArrayList<>();
|
||||
@@ -412,17 +413,22 @@ public class WorldDummyState extends BaseAppState implements MeleeAttackCallback
|
||||
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));
|
||||
applyDamage(dr, DamageCalculator.calculate(baseDamage, critMultiplier), critMultiplier > 1.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Schaden / Zustand ─────────────────────────────────────────────────────
|
||||
|
||||
private void applyDamage(DummyRuntime dr, int damage) {
|
||||
public void setWeaponSoundSystem(de.blight.game.audio.WeaponSoundSystem wss) {
|
||||
this.weaponSoundSystem = wss;
|
||||
}
|
||||
|
||||
private void applyDamage(DummyRuntime dr, int damage, boolean isCrit) {
|
||||
dr.currentHp = Math.max(0, dr.currentHp - damage);
|
||||
dr.restoreTimer = 0f;
|
||||
refreshWorldHpBar(dr);
|
||||
if (weaponSoundSystem != null) weaponSoundSystem.play(isCrit ? "crit" : "impact");
|
||||
log.info("[Dummy {}] -{} HP → {}/{}{}", dr.id, damage, dr.currentHp, dr.maxHp,
|
||||
dr.currentHp <= 0 ? " K.O.!" : "");
|
||||
if (dr.currentHp <= 0) {
|
||||
|
||||
Reference in New Issue
Block a user