Sounds für das Kampfsystem hinzugefügt
This commit is contained in:
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.
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.
@@ -52,7 +52,7 @@ dependencies {
|
||||
tasks.register('extractNatives', Copy) {
|
||||
def nativeConf = configurations.runtimeClasspath.resolvedConfiguration
|
||||
.resolvedArtifacts
|
||||
.findAll { it.name.contains('natives') }
|
||||
.findAll { it.file.name.contains('natives-linux') }
|
||||
.collect { zipTree(it.file) }
|
||||
|
||||
from nativeConf
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Gradle-Daemon läuft auf Java 21 (Kompatibilität mit Groovy 3.x ASM).
|
||||
# Source-Kompilierung erfolgt über Toolchain mit Java 26 (→ build.gradle).
|
||||
org.gradle.java.home=/usr/lib/jvm/java-21-openjdk-amd64
|
||||
org.gradle.jvmargs=--enable-native-access=ALL-UNNAMED
|
||||
org.gradle.jvmargs=--enable-native-access=ALL-UNNAMED -Djdk.lang.Process.launchMechanism=VFORK
|
||||
|
||||
Reference in New Issue
Block a user