diff --git a/Projekte/mdga/client/src/main/java/pp/mdga/client/Acoustic/AcousticHandler.java b/Projekte/mdga/client/src/main/java/pp/mdga/client/Acoustic/AcousticHandler.java index 21a7936a..b31ac71e 100644 --- a/Projekte/mdga/client/src/main/java/pp/mdga/client/Acoustic/AcousticHandler.java +++ b/Projekte/mdga/client/src/main/java/pp/mdga/client/Acoustic/AcousticHandler.java @@ -1,4 +1,166 @@ package pp.mdga.client.Acoustic; +import com.jme3.audio.AudioNode; +import com.jme3.system.NanoTimer; +import pp.mdga.client.MdgaApp; +import pp.mdga.client.MdgaState; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Random; + public class AcousticHandler { + private MdgaApp app; + + private MdgaState state = MdgaState.NONE; + + boolean playGame = false; + private ArrayList gameTracks = new ArrayList<>(); + private Random random = new Random(); + private NanoTimer trackTimer = new NanoTimer(); + + private boolean fading = false; + private boolean crossFading = false; + private GameMusic scheduled = null; + private GameMusic playing = null; + private NanoTimer fadeTimer = new NanoTimer(); + private float fadeVolume = 1.0f; + private final float FADE_DURATION = 3.0f; // Sekunden für das Fading + private final float CROSSFADE_DURATION = 1.0f; // Zeit, in der beide Tracks gleichzeitig spielen + + private float mainVolume = 1.0f; + private float musicVolume = 1.0f; + private float soundVolume = 1.0f; + + public AcousticHandler(MdgaApp app) { + this.app = app; + } + + public float getMainVolume() { + return mainVolume; + } + + public float getMusicVolume() { + return musicVolume; + } + + public float getSoundVolume() { + return soundVolume; + } + + public void update() { + updateVolumeAndTrack(); + + if(playGame) { + updateGameTracks(); + } + } + + public void playState(MdgaState state) { + if(this.state == state) { + return; + } + + MusicAsset asset = null; + + switch (state) { + case MAIN: + playGame = false; + asset = MusicAsset.MAIN_MENU; + break; + case LOBBY: + playGame = false; + asset = MusicAsset.LOBBY; + break; + case GAME: + addGameTracks(); + playGame = true; + asset = gameTracks.remove(0); + break; + case CEREMONY: + playGame = false; + asset = MusicAsset.CEREMONY; + break; + } + + assert(null != asset) : "music sceduling went wrong"; + + scheduled = new GameMusic(app, asset.getPath(), getMusicVolume() * getMainVolume(), asset.getLoop()); + } + + private float lerp(float start, float end, float t) { + return start + t * (end - start); + } + + private void updateVolumeAndTrack() { + if (playing == null && scheduled != null && !fading) { + playing = scheduled; + scheduled = null; + fadeVolume = 1.0f; + playing.play(); + return; + } + + if (scheduled != null && !fading) { + fading = true; + fadeTimer.reset(); + } + + if (fading) { + float time = fadeTimer.getTimeInSeconds(); + + if (time <= FADE_DURATION) { + float t = Math.min(time / FADE_DURATION, 1.0f); + float oldVolume = lerp(1.0f, 0.0f, t); + if (playing != null) { + playing.update(getMusicVolume() * getMainVolume() * oldVolume); + } + } + + if (time > FADE_DURATION && time <= FADE_DURATION + CROSSFADE_DURATION) { + float t = Math.min((time - FADE_DURATION) / CROSSFADE_DURATION, 1.0f); + float newVolume = lerp(0.0f, 1.0f, t); + + if (!scheduled.isPlaying()) { + scheduled.play(); + } + scheduled.update(getMusicVolume() * getMainVolume() * newVolume); + } + + if (time > FADE_DURATION + CROSSFADE_DURATION) { + if (playing != null) { + playing.pause(); + } + playing = scheduled; + scheduled = null; + + fadeVolume = 1.0f; + fading = false; + } + } else if (playing != null) { + playing.update(getMusicVolume() * getMainVolume()); + } + } + + private void addGameTracks() { + for (int i = 1; i <= 6; i++) { + gameTracks.add(MusicAsset.valueOf("GAME_" + i)); + } + Collections.shuffle(gameTracks, random); + } + + private void updateGameTracks() { + if (playing != null && playing.nearEnd(3) && trackTimer.getTimeInSeconds() > 20) { + trackTimer.reset(); + + if (gameTracks.isEmpty()) { + addGameTracks(); + } + + MusicAsset nextTrack = gameTracks.remove(0); + + scheduled = new GameMusic(app, nextTrack.getPath(), getMusicVolume() * getMainVolume(), nextTrack.getLoop()); + } + } } diff --git a/Projekte/mdga/client/src/main/java/pp/mdga/client/Acoustic/GameMusic.java b/Projekte/mdga/client/src/main/java/pp/mdga/client/Acoustic/GameMusic.java new file mode 100644 index 00000000..27814dd5 --- /dev/null +++ b/Projekte/mdga/client/src/main/java/pp/mdga/client/Acoustic/GameMusic.java @@ -0,0 +1,81 @@ +package pp.mdga.client.Acoustic; + +import com.jme3.audio.AudioData; +import com.jme3.audio.AudioNode; +import com.jme3.audio.AudioSource; +import pp.mdga.client.MdgaApp; + +public class GameMusic { + private final MdgaApp app; + + private float volume; + private AudioNode music; + + GameMusic(MdgaApp app, String path, float volume, boolean loop) { + this.app = app; + this.volume = volume; + + loadMusic(path); + + music.setLooping(loop); + } + + void play() { + if(null == music) { + return; + } + + music.play(); + } + + void pause() { + if(null == music) { + return; + } + + music.stop(); + } + + void reset() { + if(null == music) { + return; + } + + music.setTimeOffset(0); + } + + boolean isPlaying() { + return music.getStatus() == AudioSource.Status.Playing; + } + + boolean nearEnd(float thresholdSeconds) { + if (music == null || !isPlaying()) { + return false; + } + + float currentTime = music.getPlaybackTime(); // Current playback time in seconds + float duration = music.getAudioData().getDuration(); // Total duration in seconds + + if (duration <= 0) { + return false; + } + + float remainingTime = duration - currentTime; + + return remainingTime <= thresholdSeconds; + } + + void update(float newVolume) { + if(volume != newVolume) { + volume = newVolume; + music.setVolume(volume); + } + } + + private void loadMusic(String path) { + music = new AudioNode(app.getAssetManager(), path, AudioData.DataType.Stream); + music.setVolume(volume); + music.setPositional(false); + music.setDirectional(false); + } +} diff --git a/Projekte/mdga/client/src/main/java/pp/mdga/client/Acoustic/GameSound.java b/Projekte/mdga/client/src/main/java/pp/mdga/client/Acoustic/GameSound.java new file mode 100644 index 00000000..a774a944 --- /dev/null +++ b/Projekte/mdga/client/src/main/java/pp/mdga/client/Acoustic/GameSound.java @@ -0,0 +1,4 @@ +package pp.mdga.client.Acoustic; + +public class GameSound { +} diff --git a/Projekte/mdga/client/src/main/java/pp/mdga/client/Acoustic/MusicAsset.java b/Projekte/mdga/client/src/main/java/pp/mdga/client/Acoustic/MusicAsset.java new file mode 100644 index 00000000..6faecf56 --- /dev/null +++ b/Projekte/mdga/client/src/main/java/pp/mdga/client/Acoustic/MusicAsset.java @@ -0,0 +1,32 @@ +package pp.mdga.client.Acoustic; + +public enum MusicAsset { + MAIN_MENU("Spaceship.wav"), + LOBBY("DeadPlanet.wav"), + CEREMONY("80s,Disco,Life.wav"), + GAME_1("NeonRoadTrip.wav", false), + GAME_2("NoPressureTrance.wav", false), + GAME_3("TheSynthRave.wav", false), + GAME_4("LaserParty.wav", false), + GAME_5("RetroNoir.wav", false), + GAME_6("SpaceInvaders.wav", false); + + private final String path; + private final boolean loop; + + MusicAsset(String name) { + this.path = "music/" + name; + this.loop = false; + } + + MusicAsset(String name, boolean loop) { + this.path = "music/" + name; + this.loop = loop; + } + + public String getPath() { + return path; + } + + public boolean getLoop() { return loop; } +} diff --git a/Projekte/mdga/client/src/main/java/pp/mdga/client/Board/BoardView.java b/Projekte/mdga/client/src/main/java/pp/mdga/client/Board/BoardView.java index 93287e32..e6db4d7d 100644 --- a/Projekte/mdga/client/src/main/java/pp/mdga/client/Board/BoardView.java +++ b/Projekte/mdga/client/src/main/java/pp/mdga/client/Board/BoardView.java @@ -23,8 +23,9 @@ public class BoardView { private ArrayList pieces; public BoardView(int playerCount, MdgaApp mdgaApp) { - assert(2 <= playerCount && playerCount <= 4); - assert(mdgaApp != null); + assert(2 <= playerCount && playerCount <= 4) : "invalid player count"; + assert(mdgaApp != null) : "app is null"; + this.mdgaApp = mdgaApp; diff --git a/Projekte/mdga/client/src/main/java/pp/mdga/client/Board/MapLoader.java b/Projekte/mdga/client/src/main/java/pp/mdga/client/Board/MapLoader.java index 16af95fd..0b07bcd0 100644 --- a/Projekte/mdga/client/src/main/java/pp/mdga/client/Board/MapLoader.java +++ b/Projekte/mdga/client/src/main/java/pp/mdga/client/Board/MapLoader.java @@ -27,12 +27,12 @@ public List loadMap(String mapName) { if(entry.charAt(0) == '#') continue; String[] parts = entry.trim().split(" "); - if(parts.length != 2) throw new RuntimeException(); + assert(parts.length == 2) : "parts.lenghth != 2"; String assetName = parts[0]; String[] coordinates = parts[1].split(","); - if(coordinates.length != 2) throw new RuntimeException(); + assert(coordinates.length == 2) : "coordinates.lenghth != 2"; int x = Integer.parseInt(coordinates[0]); int y = Integer.parseInt(coordinates[1]); diff --git a/Projekte/mdga/client/src/main/java/pp/mdga/client/MdgaApp.java b/Projekte/mdga/client/src/main/java/pp/mdga/client/MdgaApp.java index fb3740ba..8078aa2d 100644 --- a/Projekte/mdga/client/src/main/java/pp/mdga/client/MdgaApp.java +++ b/Projekte/mdga/client/src/main/java/pp/mdga/client/MdgaApp.java @@ -1,6 +1,8 @@ package pp.mdga.client; import com.jme3.app.SimpleApplication; +import com.jme3.system.NanoTimer; +import pp.mdga.client.Acoustic.AcousticHandler; import pp.mdga.client.Animation.AnimationHandler; import com.jme3.light.AmbientLight; import com.jme3.light.DirectionalLight; @@ -20,15 +22,20 @@ public class MdgaApp extends SimpleApplication { private AnimationHandler animationHandler; + private AcousticHandler acousticHandler; private BoardView boardView; + NanoTimer test = new NanoTimer(); + private MdgaState testState = MdgaState.MAIN; + public static void main(String[] args) { - MdgaApp app = new MdgaApp(); AppSettings settings = new AppSettings(true); settings.setSamples(128); settings.setCenterWindow(true); - settings.setWidth(1300); - settings.setHeight(1000); + settings.setWidth(1280); + settings.setHeight(720); + + MdgaApp app = new MdgaApp(); app.setSettings(settings); app.setShowSettings(false); app.start(); @@ -37,8 +44,11 @@ public static void main(String[] args) { @Override public void simpleInitApp() { animationHandler = new AnimationHandler(this); + acousticHandler = new AcousticHandler(this); boardView = new BoardView(4,this); + acousticHandler.playState(MdgaState.GAME); + flyCam.setEnabled(true); int zoom = 20; cam.setLocation(new Vector3f(zoom,0,zoom)); @@ -48,20 +58,48 @@ public void simpleInitApp() { sun.setColor(ColorRGBA.White); sun.setDirection(new Vector3f(-1,0,-1)); rootNode.addLight(sun); + AmbientLight ambient = new AmbientLight(); ambient.setColor(new ColorRGBA(0.3f,0.3f,0.3f,1)); rootNode.addLight(ambient); - final int SHADOWMAP_SIZE=1024*8; + final int SHADOWMAP_SIZE= 1024 * 8; DirectionalLightShadowRenderer dlsr = new DirectionalLightShadowRenderer(assetManager, SHADOWMAP_SIZE, 4); dlsr.setLight(sun); viewPort.addProcessor(dlsr); - - } @Override public void simpleUpdate(float tpf) { - //this method will be called every game tick and can be used to make updates + acousticHandler.update(); + + test.reset(); + if(test.getTimeInSeconds() > 10){ + if(testState == MdgaState.MAIN) { + testState = MdgaState.LOBBY; + acousticHandler.playState(MdgaState.MAIN); + } else if (testState == MdgaState.LOBBY) { + testState = MdgaState.CEREMONY; + acousticHandler.playState(MdgaState.LOBBY); + } + else { + testState = MdgaState.MAIN; + acousticHandler.playState(MdgaState.CEREMONY); + } + + test.reset(); + } + } + + public AnimationHandler getAnimationHandler() { + return animationHandler; + } + + public AcousticHandler getAcousticHandler() { + return acousticHandler; + } + + public BoardView getBoardView() { + return boardView; } } diff --git a/Projekte/mdga/client/src/main/java/pp/mdga/client/MdgaState.java b/Projekte/mdga/client/src/main/java/pp/mdga/client/MdgaState.java index 7f2f609d..be3d32de 100644 --- a/Projekte/mdga/client/src/main/java/pp/mdga/client/MdgaState.java +++ b/Projekte/mdga/client/src/main/java/pp/mdga/client/MdgaState.java @@ -5,6 +5,12 @@ import pp.mdga.notification.PlayerInGameNotification; public enum MdgaState { + NONE { + @Override + void handleNotification(MdgaApp app, Notification notification) { + throw new RuntimeException("unexpected notification"); + } + }, MAIN { @Override void handleNotification(MdgaApp app, Notification notification) {