diff --git a/Projekte/battleship/client/src/main/java/pp/battleship/client/BattleshipApp.java b/Projekte/battleship/client/src/main/java/pp/battleship/client/BattleshipApp.java index 6ac1c729..36901eb7 100644 --- a/Projekte/battleship/client/src/main/java/pp/battleship/client/BattleshipApp.java +++ b/Projekte/battleship/client/src/main/java/pp/battleship/client/BattleshipApp.java @@ -122,6 +122,11 @@ public class BattleshipApp extends SimpleApplication implements BattleshipClient */ private final ActionListener escapeListener = (name, isPressed, tpf) -> escape(isPressed); + /** + * Listener for handling actions triggered by the Escape key. + */ + private GameMusic music; + static { // Configure logging LogManager manager = LogManager.getLogManager(); @@ -193,6 +198,15 @@ DialogManager getDialogManager() { return dialogManager; } + /** + * Returns the GameMusic responsible for playing music. + * + * @return The {@link GameMusic} instance. + */ + GameMusic getGameMusic() { + return music; + } + /** * Returns the game logic handler for the client. * @@ -225,6 +239,8 @@ public void simpleInitApp() { setupStates(); setupGui(); serverConnection.connect(); + + music = new GameMusic(this, "Sound/Music/battleship.ogg"); } /** diff --git a/Projekte/battleship/client/src/main/java/pp/battleship/client/GameMusic.java b/Projekte/battleship/client/src/main/java/pp/battleship/client/GameMusic.java new file mode 100644 index 00000000..deebbbef --- /dev/null +++ b/Projekte/battleship/client/src/main/java/pp/battleship/client/GameMusic.java @@ -0,0 +1,170 @@ +//////////////////////////////////////// +// Programming project code +// UniBw M, 2022, 2023, 2024 +// www.unibw.de/inf2 +// (c) Mark Minas (mark.minas@unibw.de) +//////////////////////////////////////// + +package pp.battleship.client; + +import com.jme3.app.Application; +import com.jme3.app.state.AbstractAppState; +import com.jme3.app.state.AppStateManager; +import com.jme3.asset.AssetLoadException; +import com.jme3.asset.AssetNotFoundException; +import com.jme3.audio.AudioData; +import com.jme3.audio.AudioNode; +import com.jme3.audio.AudioSource; +import pp.battleship.notification.GameEventListener; +import pp.battleship.notification.SoundEvent; + +import java.lang.System.Logger; +import java.lang.System.Logger.Level; +import java.util.prefs.Preferences; + +import static pp.util.PreferencesUtils.getPreferences; + +/** + * An application state that plays music. + */ +public class GameMusic extends AbstractAppState implements GameEventListener { + private static final Logger LOGGER = System.getLogger(GameMusic.class.getName()); + + private static final Preferences PREFERENCES = getPreferences(GameMusic.class); + private static final String ENABLED_PREF = "toggle"; //NON-NLS + private static final String SLIDER_PREF = "slider"; //NON-NLS + + private AudioNode music; + private boolean enabled; + private float volume; + + public GameMusic(Application app, String musicFilePath) { + this.enabled = PREFERENCES.getBoolean(ENABLED_PREF, true); + this.volume = PREFERENCES.getFloat(SLIDER_PREF, 2.0f); + + music = new AudioNode(app.getAssetManager(), musicFilePath, AudioData.DataType.Stream); + music.setLooping(true); + music.setPositional(false); + music.setVolume(1.0f); + + if(enabled) { + start(); + } + } + + + /** + * Checks if music is enabled in the preferences. + * + * @return {@code true} if music is enabled, {@code false} otherwise. + */ + public static boolean enabledInPreferences() { + return PREFERENCES.getBoolean(ENABLED_PREF, true); + } + + /** + * Returns the volume value that is set in the preferences. + * + * @return {@code true} if sound is enabled, {@code false} otherwise. + */ + public static float volumeInPreferences() { + return PREFERENCES.getFloat(SLIDER_PREF, 2.0f); + } + + /** + * Toggles the game music on or off. + */ + public void toggleMusic() { + setEnabled(!isEnabled()); + } + + /** + * Sets the enabled state of this AppState. + * Overrides {@link AbstractAppState#setEnabled(boolean)} + * + * @param enabled {@code true} to enable the AppState, {@code false} to disable it. + */ + @Override + public void setEnabled(boolean enabled) { + super.setEnabled(enabled); + + if(enabled) { + start(); + } else { + stop(); + } + + LOGGER.log(Level.INFO, "Music enabled: {0}", enabled); //NON-NLS + PREFERENCES.putBoolean(ENABLED_PREF, enabled); + } + + /** + * Sets the volume for the music + * + * @param volume the new volume to be set + */ + public void setVolume(float volume) { + this.volume = volume; + music.setVolume(volume); + + LOGGER.log(Level.INFO, "Volume set to: {0}", volume); //NON-NLS + PREFERENCES.putFloat(SLIDER_PREF, volume); + } + + /** + * Loads a music from the specified file. + * + * @param app The application + * @param name The name of the sound file. + * @return The loaded AudioNode. + */ + private AudioNode loadSound(Application app, String name) { + try { + final AudioNode sound = new AudioNode(app.getAssetManager(), name, AudioData.DataType.Buffer); + sound.setLooping(false); + sound.setPositional(false); + return sound; + } + catch (AssetLoadException | AssetNotFoundException ex) { + LOGGER.log(Level.ERROR, ex.getMessage(), ex); + } + return null; + } + + /** + * Starts the music. + */ + public void start() { + if (enabled && (music.getStatus() == AudioSource.Status.Stopped || music.getStatus() == AudioSource.Status.Paused)) { + music.play(); + } + } + + /** + * Stops the music. + */ + public void stop() { + if (music.getStatus() == AudioSource.Status.Playing) { + music.stop(); + } + } + + /** + * This method returns the volume + * + * @return float the current volume + */ + public float getVolume() { + return volume; + } + + /** + * Returns if music should be played or not + * + * @return boolean value if music is enabled + */ + public boolean isMusicEnabled() { + return enabled; + } + +} diff --git a/Projekte/battleship/client/src/main/java/pp/battleship/client/Menu.java b/Projekte/battleship/client/src/main/java/pp/battleship/client/Menu.java index 0e100e86..14521594 100644 --- a/Projekte/battleship/client/src/main/java/pp/battleship/client/Menu.java +++ b/Projekte/battleship/client/src/main/java/pp/battleship/client/Menu.java @@ -7,9 +7,8 @@ package pp.battleship.client; -import com.simsilica.lemur.Button; -import com.simsilica.lemur.Checkbox; -import com.simsilica.lemur.Label; +import com.simsilica.lemur.*; +import com.simsilica.lemur.core.VersionedReference; import com.simsilica.lemur.style.ElementId; import pp.dialog.Dialog; import pp.dialog.StateCheckboxModel; @@ -34,6 +33,8 @@ class Menu extends Dialog { private final Button loadButton = new Button(lookup("menu.map.load")); private final Button saveButton = new Button(lookup("menu.map.save")); + private final VersionedReference volumeRef; + /** * Constructs the Menu dialog for the Battleship application. * @@ -49,6 +50,20 @@ public Menu(BattleshipApp app) { .addClickCommands(s -> ifTopDialog(this::loadDialog)); addChild(saveButton) .addClickCommands(s -> ifTopDialog(this::saveDialog)); + + Checkbox musicToggle = new Checkbox(lookup("menu.music.toggle")); + musicToggle.setChecked(app.getGameMusic().isMusicEnabled()); + musicToggle.addClickCommands(s -> toggleMusic()); + + addChild(musicToggle); + + Slider volumeSlider = new Slider(lookup("menu.music.slider")); + volumeSlider.setModel(new DefaultRangedValueModel(0.0 , 4.0, app.getGameMusic().getVolume())); + volumeSlider.setDelta(0.4); + addChild(volumeSlider); + + volumeRef = volumeSlider.getModel().createReference(); + addChild(new Button(lookup("menu.return-to-game"))) .addClickCommands(s -> ifTopDialog(this::close)); addChild(new Button(lookup("menu.quit"))) @@ -57,12 +72,33 @@ public Menu(BattleshipApp app) { } /** - * Updates the state of the load and save buttons based on the game logic. + * Updates the state of the load/save buttons and volume based on the game logic. */ @Override public void update() { loadButton.setEnabled(app.getGameLogic().mayLoadMap()); saveButton.setEnabled(app.getGameLogic().maySaveMap()); + + if(volumeRef.update()){ + double newVolume = volumeRef.get(); + adjustVolume(newVolume); + } + } + + /** + * this method adjust the volume for the background music + * + * @param volume is the double value of the volume + */ + private void adjustVolume(double volume) { + app.getGameMusic().setVolume((float) volume); + } + + /** + * this method toggles the background music on and off + */ + private void toggleMusic() { + app.getGameMusic().toggleMusic(); } /** diff --git a/Projekte/battleship/client/src/main/resources/Sound/Music/CREDIT.txt b/Projekte/battleship/client/src/main/resources/Sound/Music/CREDIT.txt new file mode 100644 index 00000000..06580554 --- /dev/null +++ b/Projekte/battleship/client/src/main/resources/Sound/Music/CREDIT.txt @@ -0,0 +1 @@ +credit to patrickdearteaga.com diff --git a/Projekte/battleship/client/src/main/resources/Sound/Music/battleship.ogg b/Projekte/battleship/client/src/main/resources/Sound/Music/battleship.ogg new file mode 100644 index 00000000..f592ee4c Binary files /dev/null and b/Projekte/battleship/client/src/main/resources/Sound/Music/battleship.ogg differ diff --git a/Projekte/battleship/model/src/main/resources/battleship.properties b/Projekte/battleship/model/src/main/resources/battleship.properties index 47f1f6cf..ecdf197b 100644 --- a/Projekte/battleship/model/src/main/resources/battleship.properties +++ b/Projekte/battleship/model/src/main/resources/battleship.properties @@ -31,6 +31,8 @@ menu.return-to-game=Return to game menu.sound-enabled=Sound switched on menu.map.load=Load map from file... menu.map.save=Save map in file... +menu.music.toggle=Toggle music on/off +menu.music.slider=Volumeslider label.file=File: label.connecting=Connecting... dialog.error=Error diff --git a/Projekte/battleship/model/src/main/resources/battleship_de.properties b/Projekte/battleship/model/src/main/resources/battleship_de.properties index 40394202..e2a7b1fa 100644 --- a/Projekte/battleship/model/src/main/resources/battleship_de.properties +++ b/Projekte/battleship/model/src/main/resources/battleship_de.properties @@ -31,6 +31,8 @@ menu.return-to-game=Zurück zum Spiel menu.sound-enabled=Sound eingeschaltet menu.map.load=Karte von Datei laden... menu.map.save=Karte in Datei speichern... +menu.music.toggle=Musik an/ausschalten +menu.music.slider=Lautstärkeregler label.file=Datei: label.connecting=Verbindung wird aufgebaut... dialog.error=Fehler