diff --git a/Projekte/battleship/client/src/main/java/pp/battleship/client/BackgroundMusic.java b/Projekte/battleship/client/src/main/java/pp/battleship/client/BackgroundMusic.java new file mode 100644 index 00000000..9e9739d1 --- /dev/null +++ b/Projekte/battleship/client/src/main/java/pp/battleship/client/BackgroundMusic.java @@ -0,0 +1,264 @@ +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.DataType; +import com.jme3.audio.AudioNode; +import pp.battleship.notification.GameEventListener; +import pp.battleship.notification.MusicEvent; + +import java.lang.System.Logger; +import java.lang.System.Logger.Level; +import java.util.prefs.Preferences; + +import static pp.util.PreferencesUtils.getPreferences; + +/** + * The BackgroundMusic class represents the background music in the Battleship game application. + * It extends the AbstractAppState class and provides functionalities for playing the menu music, + * game music, victory music, and defeat music. + */ +public class BackgroundMusic extends AbstractAppState implements GameEventListener { + /** + * Logger for the BackgroundMusic class. + */ + private static final Logger LOGGER = System.getLogger(BackgroundMusic.class.getName()); + + /** + * Preferences for storing music settings. + */ + private static final Preferences PREFERENCES = getPreferences(BackgroundMusic.class); + + /** + * Preference key for enabling/disabling music. + */ + private static final String ENABLED_PREF = "enabled"; //NON-NLS + + /** + * Preference key for storing the volume level. + */ + private static final String VOLUME_PREF = "volume"; //NON-NLS + + /** + * Path to the menu music file. + */ + private static final String MENU_MUSIC_PATH = "Sound/Music/menu_music.ogg"; + + /** + * Path to the game music file. + */ + private static final String GAME_MUSIC_PATH = "Sound/Music/pirates.ogg"; + + /** + * Path to the victory music file. + */ + private static final String VICTORY_MUSIC_PATH = "Sound/Music/win_the_game.ogg"; + + /** + * Path to the defeat music file. + */ + private static final String DEFEAT_MUSIC_PATH = "Sound/Music/defeat.ogg"; + + /** + * AudioNode for the menu music. + */ + private AudioNode menuMusic; + + /** + * AudioNode for the game music. + */ + private AudioNode gameMusic; + + /** + * AudioNode for the victory music. + */ + private AudioNode victoryMusic; + + /** + * AudioNode for the defeat music. + */ + private AudioNode defeatMusic; + + /** + * The currently playing AudioNode. + */ + private AudioNode currentMusic; + + /** + * The volume level for the background music. + */ + private float volume; + + /** + * 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); + } + + /** + * Sets the enabled state of this AppState. + * Overrides {@link com.jme3.app.state.AbstractAppState#setEnabled(boolean)} + * + * @param enabled {@code true} to enable the AppState, {@code false} to disable it. + */ + @Override + public void setEnabled(boolean enabled) { + if (isEnabled() == enabled) return; + super.setEnabled(enabled); + LOGGER.log(Level.INFO, "Music enabled: {0}", enabled); //NON-NLS + PREFERENCES.putBoolean(ENABLED_PREF, enabled); + playCurrentMusic(); + } + + /** + * Initializes the music for the game. + * Overrides {@link AbstractAppState#initialize(AppStateManager, Application)} + * + * @param stateManager The state manager + * @param app The application + */ + @Override + public void initialize(AppStateManager stateManager, Application app) { + LOGGER.log(Level.INFO, "Initializing background music"); //NON-NLS + super.initialize(stateManager, app); + menuMusic = loadMusic(app, MENU_MUSIC_PATH); + gameMusic = loadMusic(app, GAME_MUSIC_PATH); + victoryMusic = loadMusic(app, VICTORY_MUSIC_PATH); + defeatMusic = loadMusic(app, DEFEAT_MUSIC_PATH); + currentMusic = menuMusic; + playCurrentMusic(); + } + + /** + * Loads a music file and initializes an AudioNode with the specified settings. + * + * @param app The application instance. + * @param name The name of the music file to load. + * @return The initialized AudioNode, or {@code null} if the file could not be loaded. + */ + private AudioNode loadMusic(Application app, String name) { + try { + this.volume = PREFERENCES.getFloat(VOLUME_PREF, 0.5f); + final AudioNode music = new AudioNode(app.getAssetManager(), name, DataType.Stream); + music.setLooping(true); + music.setVolume(volume); + music.setPositional(false); + music.setDirectional(false); + return music; + } catch (AssetLoadException | AssetNotFoundException ex) { + LOGGER.log(Level.ERROR, ex.getMessage(), ex); + } + return null; + } + + /** + * Plays the current music if the music is enabled. + * Stops the current music if the music is disabled. + */ + private void playCurrentMusic() { + if (isEnabled()) { + if (currentMusic != null) { + LOGGER.log(Level.INFO, "Playing current music"); //NON-NLS + currentMusic.play(); + } + } else { + if (currentMusic != null) { + currentMusic.stop(); + } + } + } + + /** + * Plays the game music. + */ + private void gameMusic() { + if (isEnabled() && gameMusic != null) { + stopAll(); + LOGGER.log(Level.INFO, "Playing game music"); //NON-NLS + gameMusic.play(); + } + } + + /** + * Plays the victory music. + */ + private void victoryMusic() { + if (isEnabled() && victoryMusic != null) { + stopAll(); + LOGGER.log(Level.INFO, "Playing victory music"); //NON-NLS + victoryMusic.play(); + } + } + + /** + * Plays the defeat music. + */ + private void defeatMusic() { + if (isEnabled() && defeatMusic != null) { + stopAll(); + LOGGER.log(Level.INFO, "Playing defeat music"); //NON-NLS + defeatMusic.play(); + } + } + + /** + * Stops all music. + */ + private void stopAll() { + if (menuMusic != null) menuMusic.stop(); + if (gameMusic != null) gameMusic.stop(); + if (victoryMusic != null) victoryMusic.stop(); + if (defeatMusic != null) defeatMusic.stop(); + } + + /** + * Handles the received music event and plays the corresponding music. + * + * @param event The music event to handle. + */ + @Override + public void receivedEvent(MusicEvent event) { + switch (event.music()) { + case GAME_MUSIC -> { + gameMusic(); + currentMusic = gameMusic; + } + case VICTORY_MUSIC -> { + victoryMusic(); + currentMusic = victoryMusic; + } + case DEFEAT_MUSIC -> { + defeatMusic(); + currentMusic = defeatMusic; + } + } + } + + /** + * Sets the volume for the background music and updates the preferences. + * + * @param volume The volume level to set. + */ + public void setVolume(float volume) { + LOGGER.log(Level.INFO, "Setting volume to {0}", volume); //NON-NLS + this.volume = volume; + currentMusic.setVolume(volume); + PREFERENCES.putFloat(VOLUME_PREF, volume); + } + + + /** + * Returns the volume level for the background music. + * + * @return The volume level as a float. + */ + public float getVolume() { + return volume; + } +} \ No newline at end of file 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..cb08373d 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 @@ -10,6 +10,7 @@ import com.jme3.app.DebugKeysAppState; import com.jme3.app.SimpleApplication; import com.jme3.app.StatsAppState; +import com.jme3.audio.AudioNode; import com.jme3.font.BitmapFont; import com.jme3.font.BitmapText; import com.jme3.input.KeyInput; @@ -128,8 +129,7 @@ public class BattleshipApp extends SimpleApplication implements BattleshipClient try { manager.readConfiguration(new FileInputStream("logging.properties")); LOGGER.log(Level.INFO, "Successfully read logging properties"); //NON-NLS - } - catch (IOException e) { + } catch (IOException e) { LOGGER.log(Level.INFO, e.getMessage()); } } @@ -267,6 +267,7 @@ private void setupStates() { stateManager.detach(stateManager.getState(DebugKeysAppState.class)); attachGameSound(); + attachBackgroundSound(); stateManager.attachAll(new EditorAppState(), new BattleAppState(), new SeaAppState()); } @@ -280,6 +281,19 @@ private void attachGameSound() { stateManager.attach(gameSound); } + /** + * Attaches the background music state and sets its initial enabled state. + * The background music state is responsible for managing the background music + * playback in the game. It listens to the game logic for any changes in the + * background music settings. + */ + private void attachBackgroundSound() { + final BackgroundMusic backgroundMusic = new BackgroundMusic(); + logic.addListener(backgroundMusic); + backgroundMusic.setEnabled(BackgroundMusic.enabledInPreferences()); + stateManager.attach(backgroundMusic); + } + /** * Updates the application state every frame. * This method is called once per frame during the game loop. @@ -405,12 +419,12 @@ public void stop(boolean waitFor) { */ void confirmDialog(String question, Runnable yesAction) { DialogBuilder.simple(dialogManager) - .setTitle(lookup("dialog.question")) - .setText(question) - .setOkButton(lookup("button.yes"), yesAction) - .setNoButton(lookup("button.no")) - .build() - .open(); + .setTitle(lookup("dialog.question")) + .setText(question) + .setOkButton(lookup("button.yes"), yesAction) + .setNoButton(lookup("button.no")) + .build() + .open(); } /** @@ -420,10 +434,10 @@ void confirmDialog(String question, Runnable yesAction) { */ void errorDialog(String errorMessage) { DialogBuilder.simple(dialogManager) - .setTitle(lookup("dialog.error")) - .setText(errorMessage) - .setOkButton(lookup("button.ok")) - .build() - .open(); + .setTitle(lookup("dialog.error")) + .setText(errorMessage) + .setOkButton(lookup("button.ok")) + .build() + .open(); } } 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..92120f8a 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 @@ -9,7 +9,10 @@ import com.simsilica.lemur.Button; import com.simsilica.lemur.Checkbox; +import com.simsilica.lemur.DefaultRangedValueModel; import com.simsilica.lemur.Label; +import com.simsilica.lemur.Slider; +import com.simsilica.lemur.core.VersionedReference; import com.simsilica.lemur.style.ElementId; import pp.dialog.Dialog; import pp.dialog.StateCheckboxModel; @@ -19,6 +22,8 @@ import java.io.IOException; import java.util.prefs.Preferences; +import java.lang.System.Logger; + import static pp.battleship.Resources.lookup; import static pp.util.PreferencesUtils.getPreferences; @@ -28,11 +33,13 @@ * returning to the game, and quitting the application. */ class Menu extends Dialog { + private static final Logger LOGGER = System.getLogger(Menu.class.getName()); private static final Preferences PREFERENCES = getPreferences(Menu.class); private static final String LAST_PATH = "last.file.path"; private final BattleshipApp app; 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. @@ -43,8 +50,19 @@ public Menu(BattleshipApp app) { super(app.getDialogManager()); this.app = app; addChild(new Label(lookup("battleship.name"), new ElementId("header"))); //NON-NLS + addChild(new Checkbox(lookup("menu.sound-enabled"), - new StateCheckboxModel(app, GameSound.class))); + new StateCheckboxModel(app, GameSound.class))); + + addChild(new Checkbox(lookup("menu.music-toggle"), + new StateCheckboxModel(app, BackgroundMusic.class))); + + Slider volumeSlider = new Slider(); + volumeSlider.setModel(new DefaultRangedValueModel(0.0, 2.0, app.getStateManager().getState(BackgroundMusic.class).getVolume())); + volumeSlider.setDelta(0.1f); + addChild(volumeSlider); + volumeRef = volumeSlider.getModel().createReference(); + addChild(loadButton) .addClickCommands(s -> ifTopDialog(this::loadDialog)); addChild(saveButton) @@ -65,6 +83,28 @@ public void update() { saveButton.setEnabled(app.getGameLogic().maySaveMap()); } + /** + * Updates the menu state based on the time per frame (tpf). + * If the volume reference has been updated, adjusts the volume accordingly. + * + * @param tpf the time per frame + */ + @Override + public void update(float tpf) { + if (volumeRef.update()) { + adjustVolume(volumeRef.get()); + } + } + + /** + * Adjusts the volume of the background music. + * + * @param volume the new volume level to set, as a double + */ + private void adjustVolume(double volume) { + app.getStateManager().getState(BackgroundMusic.class).setVolume((float) volume); + } + /** * As an escape action, this method closes the menu if it is the top dialog. */ @@ -99,8 +139,7 @@ private void handle(FileAction fileAction, TextInputDialog dialog) { PREFERENCES.put(LAST_PATH, path); fileAction.run(new File(path)); dialog.close(); - } - catch (IOException e) { + } catch (IOException e) { app.errorDialog(e.getLocalizedMessage()); } } @@ -114,13 +153,13 @@ private void handle(FileAction fileAction, TextInputDialog dialog) { private void fileDialog(FileAction fileAction, String label) { final TextInputDialog dialog = TextInputDialog.builder(app.getDialogManager()) - .setLabel(lookup("label.file")) - .setFocus(TextInputDialog::getInput) - .setTitle(label) - .setOkButton(lookup("button.ok"), d -> handle(fileAction, d)) - .setNoButton(lookup("button.cancel")) - .setOkClose(false) - .build(); + .setLabel(lookup("label.file")) + .setFocus(TextInputDialog::getInput) + .setTitle(label) + .setOkButton(lookup("button.ok"), d -> handle(fileAction, d)) + .setNoButton(lookup("button.cancel")) + .setOkClose(false) + .build(); final String path = PREFERENCES.get(LAST_PATH, null); if (path != null) dialog.getInput().setText(path.trim()); diff --git a/Projekte/battleship/client/src/main/resources/Sound/Music/README.txt b/Projekte/battleship/client/src/main/resources/Sound/Music/README.txt new file mode 100644 index 00000000..ef0266ac --- /dev/null +++ b/Projekte/battleship/client/src/main/resources/Sound/Music/README.txt @@ -0,0 +1,10 @@ +Personal-use only. + +menu_music.ogg +https://pixabay.com/de/music/szenen-aufbauen-demolition-outline-science-fiction-trailer-music-191960/ +pirates.ogg +https://pixabay.com/de/music/epische-klassik-pirates-163389/ +win_the_game.gg +https://pixabay.com/de/users/enrico_dering-31760131/ +defeat.ogg +https://pixabay.com/de/music/dramaszene-defeat-charles-michel-140604/ \ No newline at end of file diff --git a/Projekte/battleship/client/src/main/resources/Sound/Music/defeat.ogg b/Projekte/battleship/client/src/main/resources/Sound/Music/defeat.ogg new file mode 100644 index 00000000..2121eb4e Binary files /dev/null and b/Projekte/battleship/client/src/main/resources/Sound/Music/defeat.ogg differ diff --git a/Projekte/battleship/client/src/main/resources/Sound/Music/menu_music.ogg b/Projekte/battleship/client/src/main/resources/Sound/Music/menu_music.ogg new file mode 100644 index 00000000..e2f511c9 Binary files /dev/null and b/Projekte/battleship/client/src/main/resources/Sound/Music/menu_music.ogg differ diff --git a/Projekte/battleship/client/src/main/resources/Sound/Music/pirates.ogg b/Projekte/battleship/client/src/main/resources/Sound/Music/pirates.ogg new file mode 100644 index 00000000..7a8c5c6c Binary files /dev/null and b/Projekte/battleship/client/src/main/resources/Sound/Music/pirates.ogg differ diff --git a/Projekte/battleship/client/src/main/resources/Sound/Music/win_the_game.ogg b/Projekte/battleship/client/src/main/resources/Sound/Music/win_the_game.ogg new file mode 100644 index 00000000..3065bbb6 Binary files /dev/null and b/Projekte/battleship/client/src/main/resources/Sound/Music/win_the_game.ogg differ diff --git a/Projekte/battleship/model/src/main/java/pp/battleship/game/client/BattleState.java b/Projekte/battleship/model/src/main/java/pp/battleship/game/client/BattleState.java index 99df8d0b..77f721d5 100644 --- a/Projekte/battleship/model/src/main/java/pp/battleship/game/client/BattleState.java +++ b/Projekte/battleship/model/src/main/java/pp/battleship/game/client/BattleState.java @@ -11,6 +11,7 @@ import pp.battleship.message.server.EffectMessage; import pp.battleship.model.IntPoint; import pp.battleship.model.ShipMap; +import pp.battleship.notification.Music; import pp.battleship.notification.Sound; import java.lang.System.Logger.Level; @@ -62,6 +63,10 @@ public void receivedEffect(EffectMessage msg) { if (msg.isGameOver()) { msg.getRemainingOpponentShips().forEach(logic.getOpponentMap()::add); logic.setState(new GameOverState(logic)); + if (msg.isOwnShot()) + logic.playMusic(Music.VICTORY_MUSIC); + else + logic.playMusic(Music.DEFEAT_MUSIC); } } diff --git a/Projekte/battleship/model/src/main/java/pp/battleship/game/client/ClientGameLogic.java b/Projekte/battleship/model/src/main/java/pp/battleship/game/client/ClientGameLogic.java index 3170e8be..46d986a0 100644 --- a/Projekte/battleship/model/src/main/java/pp/battleship/game/client/ClientGameLogic.java +++ b/Projekte/battleship/model/src/main/java/pp/battleship/game/client/ClientGameLogic.java @@ -15,13 +15,7 @@ import pp.battleship.model.IntPoint; import pp.battleship.model.ShipMap; import pp.battleship.model.dto.ShipMapDTO; -import pp.battleship.notification.ClientStateEvent; -import pp.battleship.notification.GameEvent; -import pp.battleship.notification.GameEventBroker; -import pp.battleship.notification.GameEventListener; -import pp.battleship.notification.InfoTextEvent; -import pp.battleship.notification.Sound; -import pp.battleship.notification.SoundEvent; +import pp.battleship.notification.*; import java.io.File; import java.io.IOException; @@ -258,6 +252,15 @@ public void playSound(Sound sound) { notifyListeners(new SoundEvent(sound)); } + /** + * Emits an event to play the specified music. + * + * @param music the music to be played. + */ + public void playMusic(Music music) { + notifyListeners(new MusicEvent(music)); + } + /** * Loads a map from the specified file. * diff --git a/Projekte/battleship/model/src/main/java/pp/battleship/game/client/EditorState.java b/Projekte/battleship/model/src/main/java/pp/battleship/game/client/EditorState.java index a86839a9..417d51a1 100644 --- a/Projekte/battleship/model/src/main/java/pp/battleship/game/client/EditorState.java +++ b/Projekte/battleship/model/src/main/java/pp/battleship/game/client/EditorState.java @@ -10,7 +10,6 @@ import pp.battleship.message.client.MapMessage; import pp.battleship.model.Battleship; import pp.battleship.model.IntPoint; -import pp.battleship.model.Rotation; import pp.battleship.model.ShipMap; import pp.battleship.model.dto.ShipMapDTO; @@ -113,8 +112,7 @@ private void placeShip(IntPoint cursor) { harbor().remove(selectedInHarbor); preview = null; selectedInHarbor = null; - } - else { + } else { preview.setStatus(INVALID_PREVIEW); ownMap().add(preview); } @@ -137,8 +135,7 @@ public void clickHarbor(IntPoint pos) { harbor().add(selectedInHarbor); preview = null; selectedInHarbor = null; - } - else if (shipAtCursor != null) { + } else if (shipAtCursor != null) { selectedInHarbor = shipAtCursor; selectedInHarbor.setStatus(VALID_PREVIEW); harbor().remove(selectedInHarbor); diff --git a/Projekte/battleship/model/src/main/java/pp/battleship/game/client/WaitState.java b/Projekte/battleship/model/src/main/java/pp/battleship/game/client/WaitState.java index 13d8fc80..7eac3931 100644 --- a/Projekte/battleship/model/src/main/java/pp/battleship/game/client/WaitState.java +++ b/Projekte/battleship/model/src/main/java/pp/battleship/game/client/WaitState.java @@ -9,6 +9,7 @@ import pp.battleship.message.server.GameDetails; import pp.battleship.message.server.StartBattleMessage; +import pp.battleship.notification.Music; import java.lang.System.Logger.Level; @@ -38,6 +39,7 @@ public void receivedStartBattle(StartBattleMessage msg) { ClientGameLogic.LOGGER.log(Level.INFO, "start battle, {0} turn", msg.isMyTurn() ? "my" : "other's"); //NON-NLS logic.setInfoText(msg.getInfoTextKey()); logic.setState(new BattleState(logic, msg.isMyTurn())); + logic.playMusic(Music.GAME_MUSIC); } /** diff --git a/Projekte/battleship/model/src/main/java/pp/battleship/notification/GameEventListener.java b/Projekte/battleship/model/src/main/java/pp/battleship/notification/GameEventListener.java index 4b966793..4539f4d6 100644 --- a/Projekte/battleship/model/src/main/java/pp/battleship/notification/GameEventListener.java +++ b/Projekte/battleship/model/src/main/java/pp/battleship/notification/GameEventListener.java @@ -39,6 +39,13 @@ default void receivedEvent(InfoTextEvent event) { /* do nothing */ } */ default void receivedEvent(SoundEvent event) { /* do nothing */ } + /** + * Indicates that music shall be played. + * + * @param event the received event + */ + default void receivedEvent(MusicEvent event) { /* do nothing */ } + /** * Indicates that the client's state has changed. * diff --git a/Projekte/battleship/model/src/main/java/pp/battleship/notification/Music.java b/Projekte/battleship/model/src/main/java/pp/battleship/notification/Music.java new file mode 100644 index 00000000..e50d24ed --- /dev/null +++ b/Projekte/battleship/model/src/main/java/pp/battleship/notification/Music.java @@ -0,0 +1,23 @@ +package pp.battleship.notification; + +/** + * Enumeration representing different types of music used in the game. + */ +public enum Music { + /** + * Music for the game. + */ + GAME_MUSIC, + /** + * Music for the menu. + */ + MENU_MUSIC, + /** + * Music for victory. + */ + VICTORY_MUSIC, + /** + * Music for defeat. + */ + DEFEAT_MUSIC +} diff --git a/Projekte/battleship/model/src/main/java/pp/battleship/notification/MusicEvent.java b/Projekte/battleship/model/src/main/java/pp/battleship/notification/MusicEvent.java new file mode 100644 index 00000000..a9f06085 --- /dev/null +++ b/Projekte/battleship/model/src/main/java/pp/battleship/notification/MusicEvent.java @@ -0,0 +1,20 @@ +package pp.battleship.notification; + + +/** + * Event when music is played in the game. + * + * @param music the music to be played + */ +public record MusicEvent(Music music) implements GameEvent { + + /** + * Notifies the game event listener of this event. + * + * @param listener the game event listener + */ + @Override + public void notifyListener(GameEventListener listener) { + listener.receivedEvent(this); + } +} diff --git a/Projekte/battleship/model/src/main/resources/battleship.properties b/Projekte/battleship/model/src/main/resources/battleship.properties index 537f0d3a..7df389c1 100644 --- a/Projekte/battleship/model/src/main/resources/battleship.properties +++ b/Projekte/battleship/model/src/main/resources/battleship.properties @@ -31,6 +31,7 @@ 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=Music on/off 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 6aaeea4a..018f48a0 100644 --- a/Projekte/battleship/model/src/main/resources/battleship_de.properties +++ b/Projekte/battleship/model/src/main/resources/battleship_de.properties @@ -31,6 +31,7 @@ menu.return-to-game=Zur menu.sound-enabled=Sound eingeschaltet menu.map.load=Karte von Datei laden... menu.map.save=Karte in Datei speichern... +menu.music-toggle=Musik an/aus label.file=Datei: label.connecting=Verbindung wird aufgebaut... dialog.error=Fehler