refactor: Microphone rewrite and fix bug
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -9,6 +9,7 @@ target/
|
||||
.idea/jarRepositories.xml
|
||||
.idea/compiler.xml
|
||||
.idea/libraries/
|
||||
.idea/FuzzierSettings.xml
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
|
||||
19
pom.xml
19
pom.xml
@@ -77,6 +77,14 @@
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>be.0110.repo-releases</id>
|
||||
<name>0110.be repository</name>
|
||||
<url>https://mvn.0110.be/releases</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
@@ -153,5 +161,16 @@
|
||||
<artifactId>ffmpeg-platform</artifactId>
|
||||
<version>6.1.1-1.5.10</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>be.tarsos.dsp</groupId>
|
||||
<artifactId>core</artifactId>
|
||||
<version>2.5</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>be.tarsos.dsp</groupId>
|
||||
<artifactId>jvm</artifactId>
|
||||
<version>2.5</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
10
src/main/java/cz/jzitnik/config/MicrophoneConfig.java
Normal file
10
src/main/java/cz/jzitnik/config/MicrophoneConfig.java
Normal file
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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<Node> openSet = new PriorityQueue<>(Comparator.comparingInt(n -> n.f));
|
||||
Set<String> 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<Node> getNeighbors(Node current, RoomCords target) {
|
||||
List<Node> 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<GameRoomPart> 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());
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<SourceDataLine> 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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -84,7 +84,6 @@ public class RerenderUtils {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Remove duplicates
|
||||
for (Mob object: currentRoom.getMobs()) {
|
||||
RoomCords startObjectCords = object.getCords();
|
||||
BufferedImage texture = object.getTexture();
|
||||
|
||||
Reference in New Issue
Block a user