diff --git a/.gitignore b/.gitignore index 29a880c..5211a0b 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ target/ .idea/jarRepositories.xml .idea/compiler.xml .idea/libraries/ +.idea/FuzzierSettings.xml *.iws *.iml *.ipr diff --git a/pom.xml b/pom.xml index 9e81c13..9d4c238 100644 --- a/pom.xml +++ b/pom.xml @@ -77,6 +77,14 @@ UTF-8 + + + be.0110.repo-releases + 0110.be repository + https://mvn.0110.be/releases + + + org.projectlombok @@ -153,5 +161,16 @@ ffmpeg-platform 6.1.1-1.5.10 + + + be.tarsos.dsp + core + 2.5 + + + be.tarsos.dsp + jvm + 2.5 + diff --git a/src/main/java/cz/jzitnik/config/Debugging.java b/src/main/java/cz/jzitnik/config/Debugging.java index 8ae4227..1f46f8b 100644 --- a/src/main/java/cz/jzitnik/config/Debugging.java +++ b/src/main/java/cz/jzitnik/config/Debugging.java @@ -6,6 +6,6 @@ import lombok.Getter; @Getter @Config public class Debugging { - private final boolean renderColliders = true; - private final boolean renderPlayerCollider = true; + private final boolean renderColliders = false; + private final boolean renderPlayerCollider = false; } diff --git a/src/main/java/cz/jzitnik/config/MicrophoneConfig.java b/src/main/java/cz/jzitnik/config/MicrophoneConfig.java new file mode 100644 index 0000000..add5dc6 --- /dev/null +++ b/src/main/java/cz/jzitnik/config/MicrophoneConfig.java @@ -0,0 +1,10 @@ +package cz.jzitnik.config; + +import cz.jzitnik.annotations.Config; +import lombok.Getter; + +@Getter +@Config +public class MicrophoneConfig { + private final float volumeThreshold = 3f; +} diff --git a/src/main/java/cz/jzitnik/game/mobs/HittableMob.java b/src/main/java/cz/jzitnik/game/mobs/HittableMob.java index 8c74fb0..448ecd8 100644 --- a/src/main/java/cz/jzitnik/game/mobs/HittableMob.java +++ b/src/main/java/cz/jzitnik/game/mobs/HittableMob.java @@ -5,7 +5,6 @@ import cz.jzitnik.annotations.injectors.InjectConfig; import cz.jzitnik.annotations.injectors.InjectDependency; import cz.jzitnik.annotations.injectors.InjectState; import cz.jzitnik.config.Debugging; -import cz.jzitnik.events.FullRoomDraw; import cz.jzitnik.events.RerenderScreen; import cz.jzitnik.game.GameRoom; import cz.jzitnik.game.GameState; diff --git a/src/main/java/cz/jzitnik/game/mobs/tasks/AStarAlg.java b/src/main/java/cz/jzitnik/game/mobs/tasks/AStarAlg.java index 394e800..d03a13d 100644 --- a/src/main/java/cz/jzitnik/game/mobs/tasks/AStarAlg.java +++ b/src/main/java/cz/jzitnik/game/mobs/tasks/AStarAlg.java @@ -7,9 +7,6 @@ import java.util.*; public class AStarAlg { - // Boundaries matching your Player switch statement - // Player: if (x <= 30) return -> means 30 is the edge, valid area is > 30? - // User Update: "he can be on 30". So valid range is [30, 155] private static final int MIN_X = 30; private static final int MAX_X = 155; private static final int MIN_Y = 10; @@ -19,14 +16,12 @@ public class AStarAlg { PriorityQueue openSet = new PriorityQueue<>(Comparator.comparingInt(n -> n.f)); Set closedSet = new HashSet<>(); - // We use Chebyshev distance for the heuristic (best for 8-way movement) Node startNode = new Node(start.getX(), start.getY(), 0, getHeuristic(start, target), null); openSet.add(startNode); while (!openSet.isEmpty()) { Node current = openSet.poll(); - // Reached target? if (current.x == target.getX() && current.y == target.getY()) { return reconstructPath(current); } @@ -39,13 +34,7 @@ public class AStarAlg { String neighborKey = neighbor.x + "," + neighbor.y; if (closedSet.contains(neighborKey)) continue; - // Check collisions and boundaries if (!isValidPosition(neighbor.x, neighbor.y, colliders)) { - // EDGE CASE FIX: - // If the Player (target) is standing exactly on a wall/edge that the Mob considers invalid, - // A* will usually fail. - // We add a check: if this neighbor IS the target, we allow it. - // This lets the mob walk right up to the player's face even if they are hugging the wall. if (neighbor.x != target.getX() || neighbor.y != target.getY()) { continue; } @@ -65,7 +54,6 @@ public class AStarAlg { private static List getNeighbors(Node current, RoomCords target) { List neighbors = new ArrayList<>(); - // Added Diagonal Directions int[][] directions = { {0, 1}, {0, -1}, {1, 0}, {-1, 0}, // Up, Down, Right, Left {1, 1}, {1, -1}, {-1, 1}, {-1, -1} // Diagonals @@ -81,14 +69,10 @@ public class AStarAlg { } private static boolean isValidPosition(int x, int y, List colliders) { - // Updated checks to be inclusive so 30 is valid. - // Valid X: 30 to 155 if (x < MIN_X || x > MAX_X) return false; - // Valid Y: 10 to 110 if (y < MIN_Y || y > MAX_Y) return false; - // Check Colliders RoomCords temp = new RoomCords(x, y); for (GameRoomPart part : colliders) { if (part.isWithin(temp)) { @@ -109,7 +93,6 @@ public class AStarAlg { return path; } - // Changed to Chebyshev Distance for better diagonal estimation private static int getHeuristic(RoomCords a, RoomCords b) { int dx = Math.abs(a.getX() - b.getX()); int dy = Math.abs(a.getY() - b.getY()); diff --git a/src/main/java/cz/jzitnik/game/mobs/tasks/BlindMobFollowingPlayerTask.java b/src/main/java/cz/jzitnik/game/mobs/tasks/BlindMobFollowingPlayerTask.java index a8fe416..8c0294b 100644 --- a/src/main/java/cz/jzitnik/game/mobs/tasks/BlindMobFollowingPlayerTask.java +++ b/src/main/java/cz/jzitnik/game/mobs/tasks/BlindMobFollowingPlayerTask.java @@ -4,12 +4,13 @@ import cz.jzitnik.annotations.injectors.InjectConfig; import cz.jzitnik.annotations.injectors.InjectDependency; import cz.jzitnik.annotations.injectors.InjectState; import cz.jzitnik.config.Debugging; +import cz.jzitnik.config.MicrophoneConfig; import cz.jzitnik.game.GameState; import cz.jzitnik.game.ResourceManager; import cz.jzitnik.game.mobs.Mob; import cz.jzitnik.game.utils.RoomCords; +import cz.jzitnik.states.MicrophoneState; import cz.jzitnik.states.ScreenBuffer; -import cz.jzitnik.states.SoundState; import cz.jzitnik.states.TerminalState; import cz.jzitnik.utils.events.EventManager; import cz.jzitnik.utils.roomtasks.RoomTask; @@ -46,11 +47,14 @@ public class BlindMobFollowingPlayerTask extends RoomTask { private Debugging debugging; @InjectState - private SoundState soundState; + private MicrophoneState microphoneState; + + @InjectConfig + private MicrophoneConfig microphoneConfig; @Override public void run() { - if (playerCords == null || (soundState.isMicrophoneSetup() && soundState.getSoundVolume() > 2f)) { + if (playerCords == null || (microphoneState.isMicrophoneSetup() && microphoneState.getMicrophoneVolume() > microphoneConfig.getVolumeThreshold())) { playerCords = gameState.getPlayer().getPlayerCords().clone(); } diff --git a/src/main/java/cz/jzitnik/game/setup/GameSetup.java b/src/main/java/cz/jzitnik/game/setup/GameSetup.java index 7a9c427..8f2fbb9 100644 --- a/src/main/java/cz/jzitnik/game/setup/GameSetup.java +++ b/src/main/java/cz/jzitnik/game/setup/GameSetup.java @@ -20,7 +20,7 @@ public class GameSetup { private DependencyManager dependencyManager; public void setup() { - //gameState.setScreen(new IntroScene(dependencyManager)); + gameState.setScreen(new IntroScene(dependencyManager)); GameRoom mainRoom = new MainRoom(dependencyManager, resourceManager); GameRoom rightRoom = new GameRoom(ResourceManager.Resource.ROOM2); diff --git a/src/main/java/cz/jzitnik/game/setup/mobs/Zombie.java b/src/main/java/cz/jzitnik/game/setup/mobs/Zombie.java index 079c704..79d32e8 100644 --- a/src/main/java/cz/jzitnik/game/setup/mobs/Zombie.java +++ b/src/main/java/cz/jzitnik/game/setup/mobs/Zombie.java @@ -2,6 +2,7 @@ package cz.jzitnik.game.setup.mobs; import cz.jzitnik.game.ResourceManager; import cz.jzitnik.game.mobs.HittableMob; +import cz.jzitnik.game.mobs.tasks.BlindMobFollowingPlayerTask; import cz.jzitnik.game.mobs.tasks.MobFollowingPlayerTask; import cz.jzitnik.game.utils.RoomCords; import lombok.extern.slf4j.Slf4j; @@ -11,8 +12,8 @@ public class Zombie extends HittableMob { public Zombie(ResourceManager resourceManager, RoomCords cords) { super(resourceManager.getResource(ResourceManager.Resource.PLAYER_FRONT), null, cords, 10); - //setTask(new BlindMobFollowingPlayerTask(this, 1, 100)); - setTask(new MobFollowingPlayerTask(this, 1, 100)); + setTask(new BlindMobFollowingPlayerTask(this, 1, 100)); + //setTask(new MobFollowingPlayerTask(this, 1, 100)); } @Override diff --git a/src/main/java/cz/jzitnik/screens/scenes/Scene.java b/src/main/java/cz/jzitnik/screens/scenes/Scene.java index c11ec1d..6c390a0 100644 --- a/src/main/java/cz/jzitnik/screens/scenes/Scene.java +++ b/src/main/java/cz/jzitnik/screens/scenes/Scene.java @@ -51,7 +51,7 @@ public abstract class Scene extends Screen { if (!isRenderedAlready) { isRenderedAlready = true; render(); - } else if (currentPart != null && !onEndAction.getClass().equals(OnEndAction.Repeat.class)) { + } else if (currentPart != null && onEndAction.getClass().equals(OnEndAction.Repeat.class)) { currentPart.fullRender(); } } @@ -77,7 +77,7 @@ public abstract class Scene extends Screen { } else if (onEndAction.getClass().equals(OnEndAction.SwitchToScreen.class)) { OnEndAction.SwitchToScreen switchToScreen = (OnEndAction.SwitchToScreen) onEndAction; gameState.setScreen(switchToScreen.getScreen()); - eventManager.emitEvent(new FullRoomDraw(true)); + switchToScreen.getScreen().fullRender(); } } diff --git a/src/main/java/cz/jzitnik/sound/SoundPlayer.java b/src/main/java/cz/jzitnik/sound/SoundPlayer.java index 71b481a..ea18f7a 100644 --- a/src/main/java/cz/jzitnik/sound/SoundPlayer.java +++ b/src/main/java/cz/jzitnik/sound/SoundPlayer.java @@ -2,7 +2,6 @@ package cz.jzitnik.sound; import javax.sound.sampled.*; -import javax.sound.sampled.*; import java.io.IOException; import java.util.concurrent.atomic.AtomicReference; @@ -13,7 +12,7 @@ public class SoundPlayer { private final AtomicReference currentLine = new AtomicReference<>(); public void playSound(String filePath, int backendVolume, int masterVolume) { - long threadId = Thread.currentThread().getId(); + long threadId = Thread.currentThread().threadId(); if (!filePath.endsWith(".ogg") || masterVolume == 0) { return; diff --git a/src/main/java/cz/jzitnik/states/SoundState.java b/src/main/java/cz/jzitnik/states/MicrophoneState.java similarity index 68% rename from src/main/java/cz/jzitnik/states/SoundState.java rename to src/main/java/cz/jzitnik/states/MicrophoneState.java index ebc8b47..1095ddd 100644 --- a/src/main/java/cz/jzitnik/states/SoundState.java +++ b/src/main/java/cz/jzitnik/states/MicrophoneState.java @@ -5,7 +5,7 @@ import lombok.Data; @Data @State -public class SoundState { - private double soundVolume; +public class MicrophoneState { + private double microphoneVolume; private boolean microphoneSetup = false; } diff --git a/src/main/java/cz/jzitnik/threads/MicrophoneThread.java b/src/main/java/cz/jzitnik/threads/MicrophoneThread.java index 163a91b..16f5041 100644 --- a/src/main/java/cz/jzitnik/threads/MicrophoneThread.java +++ b/src/main/java/cz/jzitnik/threads/MicrophoneThread.java @@ -1,78 +1,73 @@ package cz.jzitnik.threads; +import be.tarsos.dsp.AudioDispatcher; +import be.tarsos.dsp.AudioEvent; +import be.tarsos.dsp.AudioProcessor; +import be.tarsos.dsp.filters.HighPass; +import be.tarsos.dsp.io.jvm.AudioDispatcherFactory; import cz.jzitnik.annotations.ThreadRegistry; import cz.jzitnik.annotations.injectors.InjectState; -import cz.jzitnik.states.SoundState; +import cz.jzitnik.states.MicrophoneState; import cz.jzitnik.utils.ShutdownableThread; import lombok.extern.slf4j.Slf4j; -import javax.sound.sampled.*; +import javax.sound.sampled.LineUnavailableException; @Slf4j @ThreadRegistry public class MicrophoneThread extends ShutdownableThread { @InjectState - private SoundState soundState; + private MicrophoneState microphoneState; - private volatile boolean running = true; + private AudioDispatcher dispatcher; @Override public void run() { - AudioFormat format = new AudioFormat(44100, 16, 1, true, false); - DataLine.Info info = new DataLine.Info(TargetDataLine.class, format); - - if (!AudioSystem.isLineSupported(info)) { - log.error("Line not supported: {}", info); - return; - } - - TargetDataLine line = null; - try { - line = (TargetDataLine) AudioSystem.getLine(info); - line.open(format); - line.start(); + dispatcher = AudioDispatcherFactory.fromDefaultMicrophone(44100, 2048, 0); - byte[] buffer = new byte[2048]; + dispatcher.addAudioProcessor(new HighPass(120, 44100)); - while (running && !Thread.currentThread().isInterrupted()) { - int bytesRead = line.read(buffer, 0, buffer.length); + dispatcher.addAudioProcessor(new AudioProcessor() { + private static final double NOISE_GATE_THRESHOLD = 1.5; - if (bytesRead > 0) { - double volume = calculateRMS(buffer, bytesRead); - soundState.setSoundVolume(volume); - soundState.setMicrophoneSetup(true); + @Override + public boolean process(AudioEvent audioEvent) { + double rms = audioEvent.getRMS(); + + double volume = rms * 100; + + if (volume < NOISE_GATE_THRESHOLD) { + volume = 0; + } + + microphoneState.setMicrophoneVolume(volume); + microphoneState.setMicrophoneSetup(true); + + return true; } - } + + @Override + public void processingFinished() { + log.info("Microphone processing stopped."); + } + }); + + dispatcher.run(); + } catch (LineUnavailableException e) { log.error("Microphone line unavailable: {}", e.getMessage()); - } finally { - if (line != null) { - line.stop(); - line.close(); - } + } catch (Exception e) { + log.error("Error in MicrophoneThread: {}", e.getMessage(), e); } } + @Override public void shutdown() { - this.running = false; + if (dispatcher != null && !dispatcher.isStopped()) { + dispatcher.stop(); + } + this.interrupt(); } - - private double calculateRMS(byte[] buffer, int bytesRead) { - long sum = 0; - - for (int i = 0; i < bytesRead; i += 2) { - int low = buffer[i]; - int high = buffer[i + 1]; - - int sample = (high << 8) | (low & 0xFF); - - sum += (long) sample * sample; - } - - double rms = Math.sqrt(sum / (bytesRead / 2.0)); - - return (rms / 32768.0) * 100; - } } diff --git a/src/main/java/cz/jzitnik/utils/DependencyManager.java b/src/main/java/cz/jzitnik/utils/DependencyManager.java index eb35f70..acae27d 100644 --- a/src/main/java/cz/jzitnik/utils/DependencyManager.java +++ b/src/main/java/cz/jzitnik/utils/DependencyManager.java @@ -1,7 +1,7 @@ package cz.jzitnik.utils; -// Don't blame me that I'm using field injection instead of construction injection. I just like it more leave me alone. -// Yes I know I'll suffer in the unit tests. (who said there will be any? hmmm) +// Don't blame me that I'm using field injection instead of construction injection. I just like it more, leave me alone. +// Yes, I know I'll suffer in the unit tests. (who said there will be any? hmmm) import com.google.common.collect.ClassToInstanceMap; import com.google.common.collect.MutableClassToInstanceMap; diff --git a/src/main/java/cz/jzitnik/utils/RerenderUtils.java b/src/main/java/cz/jzitnik/utils/RerenderUtils.java index 7e4326a..b3dcc85 100644 --- a/src/main/java/cz/jzitnik/utils/RerenderUtils.java +++ b/src/main/java/cz/jzitnik/utils/RerenderUtils.java @@ -84,7 +84,6 @@ public class RerenderUtils { } } - // TODO: Remove duplicates for (Mob object: currentRoom.getMobs()) { RoomCords startObjectCords = object.getCords(); BufferedImage texture = object.getTexture();