diff --git a/blight-assets/src/main/resources/audio/weapons/sword/attack/aussortiert/swoosh2.ogg b/blight-assets/src/main/resources/audio/weapons/sword/attack/aussortiert/swoosh2.ogg new file mode 100644 index 0000000..5343c49 Binary files /dev/null and b/blight-assets/src/main/resources/audio/weapons/sword/attack/aussortiert/swoosh2.ogg differ diff --git a/blight-assets/src/main/resources/audio/weapons/sword/attack/aussortiert/swoosh3.ogg b/blight-assets/src/main/resources/audio/weapons/sword/attack/aussortiert/swoosh3.ogg new file mode 100644 index 0000000..4cc6648 Binary files /dev/null and b/blight-assets/src/main/resources/audio/weapons/sword/attack/aussortiert/swoosh3.ogg differ diff --git a/blight-assets/src/main/resources/audio/weapons/sword/attack/aussortiert/swoosh4.ogg b/blight-assets/src/main/resources/audio/weapons/sword/attack/aussortiert/swoosh4.ogg new file mode 100644 index 0000000..0134924 Binary files /dev/null and b/blight-assets/src/main/resources/audio/weapons/sword/attack/aussortiert/swoosh4.ogg differ diff --git a/blight-assets/src/main/resources/audio/weapons/sword/attack/aussortiert/swoosh5.ogg b/blight-assets/src/main/resources/audio/weapons/sword/attack/aussortiert/swoosh5.ogg new file mode 100644 index 0000000..22f4e25 Binary files /dev/null and b/blight-assets/src/main/resources/audio/weapons/sword/attack/aussortiert/swoosh5.ogg differ diff --git a/blight-assets/src/main/resources/audio/weapons/sword/attack/swoosh1.ogg b/blight-assets/src/main/resources/audio/weapons/sword/attack/swoosh1.ogg new file mode 100644 index 0000000..76e37c0 Binary files /dev/null and b/blight-assets/src/main/resources/audio/weapons/sword/attack/swoosh1.ogg differ diff --git a/blight-assets/src/main/resources/audio/weapons/sword/attack/swoosh6.ogg b/blight-assets/src/main/resources/audio/weapons/sword/attack/swoosh6.ogg new file mode 100644 index 0000000..37d68ff Binary files /dev/null and b/blight-assets/src/main/resources/audio/weapons/sword/attack/swoosh6.ogg differ diff --git a/blight-assets/src/main/resources/audio/weapons/sword/attack/swoosh7.ogg b/blight-assets/src/main/resources/audio/weapons/sword/attack/swoosh7.ogg new file mode 100644 index 0000000..7d8a8e3 Binary files /dev/null and b/blight-assets/src/main/resources/audio/weapons/sword/attack/swoosh7.ogg differ diff --git a/blight-assets/src/main/resources/audio/weapons/sword/blocked/Sword Blocked 1.ogg b/blight-assets/src/main/resources/audio/weapons/sword/blocked/Sword Blocked 1.ogg new file mode 100644 index 0000000..b0cb151 Binary files /dev/null and b/blight-assets/src/main/resources/audio/weapons/sword/blocked/Sword Blocked 1.ogg differ diff --git a/blight-assets/src/main/resources/audio/weapons/sword/blocked/Sword Blocked 2.ogg b/blight-assets/src/main/resources/audio/weapons/sword/blocked/Sword Blocked 2.ogg new file mode 100644 index 0000000..14827cd Binary files /dev/null and b/blight-assets/src/main/resources/audio/weapons/sword/blocked/Sword Blocked 2.ogg differ diff --git a/blight-assets/src/main/resources/audio/weapons/sword/blocked/Sword Blocked 3.ogg b/blight-assets/src/main/resources/audio/weapons/sword/blocked/Sword Blocked 3.ogg new file mode 100644 index 0000000..4db83c1 Binary files /dev/null and b/blight-assets/src/main/resources/audio/weapons/sword/blocked/Sword Blocked 3.ogg differ diff --git a/blight-assets/src/main/resources/audio/weapons/sword/crit/Sword Impact Hit 2.ogg b/blight-assets/src/main/resources/audio/weapons/sword/crit/Sword Impact Hit 2.ogg new file mode 100644 index 0000000..48b4064 Binary files /dev/null and b/blight-assets/src/main/resources/audio/weapons/sword/crit/Sword Impact Hit 2.ogg differ diff --git a/blight-assets/src/main/resources/audio/weapons/sword/impact/Sword Impact Hit 1.ogg b/blight-assets/src/main/resources/audio/weapons/sword/impact/Sword Impact Hit 1.ogg new file mode 100644 index 0000000..f1eb241 Binary files /dev/null and b/blight-assets/src/main/resources/audio/weapons/sword/impact/Sword Impact Hit 1.ogg differ diff --git a/blight-assets/src/main/resources/audio/weapons/sword/impact/Sword Impact Hit 3.ogg b/blight-assets/src/main/resources/audio/weapons/sword/impact/Sword Impact Hit 3.ogg new file mode 100644 index 0000000..6801250 Binary files /dev/null and b/blight-assets/src/main/resources/audio/weapons/sword/impact/Sword Impact Hit 3.ogg differ diff --git a/blight-assets/src/main/resources/audio/weapons/sword/parry/Sword Parry 1.ogg b/blight-assets/src/main/resources/audio/weapons/sword/parry/Sword Parry 1.ogg new file mode 100644 index 0000000..47c8291 Binary files /dev/null and b/blight-assets/src/main/resources/audio/weapons/sword/parry/Sword Parry 1.ogg differ diff --git a/blight-assets/src/main/resources/audio/weapons/sword/parry/Sword Parry 2.ogg b/blight-assets/src/main/resources/audio/weapons/sword/parry/Sword Parry 2.ogg new file mode 100644 index 0000000..a4d462b Binary files /dev/null and b/blight-assets/src/main/resources/audio/weapons/sword/parry/Sword Parry 2.ogg differ diff --git a/blight-assets/src/main/resources/audio/weapons/sword/parry/Sword Parry 3.ogg b/blight-assets/src/main/resources/audio/weapons/sword/parry/Sword Parry 3.ogg new file mode 100644 index 0000000..9c20346 Binary files /dev/null and b/blight-assets/src/main/resources/audio/weapons/sword/parry/Sword Parry 3.ogg differ diff --git a/blight-assets/src/main/resources/audio/weapons/sword/sheath/Sword Sheath 1.ogg b/blight-assets/src/main/resources/audio/weapons/sword/sheath/Sword Sheath 1.ogg new file mode 100644 index 0000000..6e136d3 Binary files /dev/null and b/blight-assets/src/main/resources/audio/weapons/sword/sheath/Sword Sheath 1.ogg differ diff --git a/blight-assets/src/main/resources/audio/weapons/sword/sheath/Sword Sheath 2.ogg b/blight-assets/src/main/resources/audio/weapons/sword/sheath/Sword Sheath 2.ogg new file mode 100644 index 0000000..f364e66 Binary files /dev/null and b/blight-assets/src/main/resources/audio/weapons/sword/sheath/Sword Sheath 2.ogg differ diff --git a/blight-assets/src/main/resources/audio/weapons/sword/unsheath/Sword Unsheath 1.ogg b/blight-assets/src/main/resources/audio/weapons/sword/unsheath/Sword Unsheath 1.ogg new file mode 100644 index 0000000..90e36c3 Binary files /dev/null and b/blight-assets/src/main/resources/audio/weapons/sword/unsheath/Sword Unsheath 1.ogg differ diff --git a/blight-assets/src/main/resources/audio/weapons/sword/unsheath/Sword Unsheath 2.ogg b/blight-assets/src/main/resources/audio/weapons/sword/unsheath/Sword Unsheath 2.ogg new file mode 100644 index 0000000..ea3ebff Binary files /dev/null and b/blight-assets/src/main/resources/audio/weapons/sword/unsheath/Sword Unsheath 2.ogg differ diff --git a/blight-editor/build.gradle b/blight-editor/build.gradle index fe6a69d..678bd16 100644 --- a/blight-editor/build.gradle +++ b/blight-editor/build.gradle @@ -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 diff --git a/blight-game/src/main/java/de/blight/game/audio/WeaponSoundSystem.java b/blight-game/src/main/java/de/blight/game/audio/WeaponSoundSystem.java new file mode 100644 index 0000000..b871a40 --- /dev/null +++ b/blight-game/src/main/java/de/blight/game/audio/WeaponSoundSystem.java @@ -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> 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 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 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 scanDir(AssetManager am, Node parentNode, + Path assetRoot, String relDir) { + Path absDir = assetRoot.resolve(relDir); + if (!Files.isDirectory(absDir)) return Collections.emptyList(); + List 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 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; + } +} diff --git a/blight-game/src/main/java/de/blight/game/control/PlayerInputControl.java b/blight-game/src/main/java/de/blight/game/control/PlayerInputControl.java index 7924ac0..9cf16b4 100644 --- a/blight-game/src/main/java/de/blight/game/control/PlayerInputControl.java +++ b/blight-game/src/main/java/de/blight/game/control/PlayerInputControl.java @@ -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 surfaceQuery; private java.util.function.BiFunction 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 query) { this.waterDepthQuery = query; } diff --git a/blight-game/src/main/java/de/blight/game/scene/WorldScene.java b/blight-game/src/main/java/de/blight/game/scene/WorldScene.java index 89bf2c7..9ee8837 100644 --- a/blight-game/src/main/java/de/blight/game/scene/WorldScene.java +++ b/blight-game/src/main/java/de/blight/game/scene/WorldScene.java @@ -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); diff --git a/blight-game/src/main/java/de/blight/game/state/WorldDummyState.java b/blight-game/src/main/java/de/blight/game/state/WorldDummyState.java index e413cc3..1a8dd3d 100644 --- a/blight-game/src/main/java/de/blight/game/state/WorldDummyState.java +++ b/blight-game/src/main/java/de/blight/game/state/WorldDummyState.java @@ -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 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) { diff --git a/gradle.properties b/gradle.properties index eadbf74..26f3627 100644 --- a/gradle.properties +++ b/gradle.properties @@ -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