diff --git a/forge-gui-desktop/src/main/java/forge/screens/home/settings/CSubmenuPreferences.java b/forge-gui-desktop/src/main/java/forge/screens/home/settings/CSubmenuPreferences.java index 39e16160f03..893a093f37c 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/home/settings/CSubmenuPreferences.java +++ b/forge-gui-desktop/src/main/java/forge/screens/home/settings/CSubmenuPreferences.java @@ -27,6 +27,8 @@ import org.apache.commons.lang3.tuple.Pair; import javax.swing.*; import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.io.File; @@ -262,6 +264,7 @@ public enum CSubmenuPreferences implements ICDoc { initializeAutoUpdaterComboBox(); initializeMulliganRuleComboBox(); initializeAiProfilesComboBox(); + initializeSoundSetsComboBox(); initializeStackAdditionsComboBox(); initializeLandPlayedComboBox(); initializeColorIdentityCombobox(); @@ -466,6 +469,20 @@ public enum CSubmenuPreferences implements ICDoc { panel.setComboBox(comboBox, selectedItem); } + private void initializeSoundSetsComboBox() { + final FPref userSetting = FPref.UI_CURRENT_SOUND_SET; + final FComboBoxPanel panel = this.view.getSoundSetsComboBoxPanel(); + final FComboBox comboBox = createComboBox(SoundSystem.getAvailableSoundSets(), userSetting); + final String selectedItem = this.prefs.getPref(userSetting); + panel.setComboBox(comboBox, selectedItem); + comboBox.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent actionEvent) { + SoundSystem.invalidateSoundCache(); + } + }); + } + private void initializeCardArtPreference() { final String latestOpt = Localizer.getInstance().getMessage("latestArtOpt"); final String originalOpt = Localizer.getInstance().getMessage("originalArtOpt"); diff --git a/forge-gui-desktop/src/main/java/forge/screens/home/settings/VSubmenuPreferences.java b/forge-gui-desktop/src/main/java/forge/screens/home/settings/VSubmenuPreferences.java index 31934975948..fdcd35780b7 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/home/settings/VSubmenuPreferences.java +++ b/forge-gui-desktop/src/main/java/forge/screens/home/settings/VSubmenuPreferences.java @@ -128,6 +128,7 @@ public enum VSubmenuPreferences implements IVSubmenu { private final FComboBoxPanel cbpCardArtFormat = new FComboBoxPanel<>(localizer.getMessage("cbpCardArtFormat")+":"); private final FComboBoxPanel cbpCardArtPreference = new FComboBoxPanel<>(localizer.getMessage("lblPreferredArt")+":"); private final FComboBoxPanel cbpMulliganRule = new FComboBoxPanel<>(localizer.getMessage("cbpMulliganRule")+":"); + private final FComboBoxPanel cbpSoundSets = new FComboBoxPanel<>(localizer.getMessage("cbpSoundSets")+":"); private final FComboBoxPanel cbpAiProfiles = new FComboBoxPanel<>(localizer.getMessage("cbpAiProfiles")+":"); private final FComboBoxPanel cbpStackAdditions = new FComboBoxPanel<>(localizer.getMessage("cbpStackAdditions")+":"); private final FComboBoxPanel cbpLandPlayed = new FComboBoxPanel<>(localizer.getMessage("cbpLandPlayed")+":"); @@ -414,6 +415,9 @@ public enum VSubmenuPreferences implements IVSubmenu { pnlPrefs.add(cbEnableSounds, titleConstraints); pnlPrefs.add(new NoteLabel(localizer.getMessage("nlEnableSounds")), descriptionConstraints); + pnlPrefs.add(cbpSoundSets, comboBoxConstraints); + pnlPrefs.add(new NoteLabel(localizer.getMessage("nlpSoundSets")), descriptionConstraints); + pnlPrefs.add(cbEnableMusic, titleConstraints); pnlPrefs.add(new NoteLabel(localizer.getMessage("nlEnableMusic")), descriptionConstraints); @@ -740,6 +744,10 @@ public enum VSubmenuPreferences implements IVSubmenu { return cbpMulliganRule; } + public FComboBoxPanel getSoundSetsComboBoxPanel() { + return cbpSoundSets; + } + public FComboBoxPanel getAiProfilesComboBoxPanel() { return cbpAiProfiles; } diff --git a/forge-gui-desktop/src/main/java/forge/sound/AudioClip.java b/forge-gui-desktop/src/main/java/forge/sound/AudioClip.java index ac2e0be5db5..110340b7f7b 100644 --- a/forge-gui-desktop/src/main/java/forge/sound/AudioClip.java +++ b/forge-gui-desktop/src/main/java/forge/sound/AudioClip.java @@ -40,9 +40,6 @@ import javax.sound.sampled.UnsupportedAudioFileException; import com.google.common.io.Files; import com.sipgate.mp3wav.Converter; -import forge.localinstance.properties.ForgeConstants; - - /** * SoundSystem - a simple sound playback system for Forge. * Do not use directly. Instead, use the {@link forge.sound.SoundEffectType} enumeration. @@ -64,7 +61,7 @@ public class AudioClip implements IAudioClip { } public static boolean fileExists(String fileName) { - File fSound = new File(ForgeConstants.SOUND_DIR, fileName); + File fSound = new File(SoundSystem.getSoundDirectory(), fileName); return fSound.exists(); } @@ -195,7 +192,7 @@ public class AudioClip implements IAudioClip { } private Clip createClip(String filename) { - File fSound = new File(ForgeConstants.SOUND_DIR, filename); + File fSound = new File(SoundSystem.getSoundDirectory(), filename); if (!fSound.exists()) { throw new IllegalArgumentException("Sound file " + fSound.toString() + " does not exist, cannot make a clip of it"); } diff --git a/forge-gui-mobile/src/forge/GuiMobile.java b/forge-gui-mobile/src/forge/GuiMobile.java index b4982774bd0..041881a9260 100644 --- a/forge-gui-mobile/src/forge/GuiMobile.java +++ b/forge-gui-mobile/src/forge/GuiMobile.java @@ -21,10 +21,7 @@ import forge.screens.LoadingOverlay; import forge.screens.match.MatchController; import forge.screens.quest.QuestMenu; import forge.screens.settings.GuiDownloader; -import forge.sound.AudioClip; -import forge.sound.AudioMusic; -import forge.sound.IAudioClip; -import forge.sound.IAudioMusic; +import forge.sound.*; import forge.toolbox.FOptionPane; import forge.toolbox.GuiChoose; import forge.util.*; @@ -272,7 +269,7 @@ public class GuiMobile implements IGuiBase { @Override public IAudioClip createAudioClip(final String filename) { - return AudioClip.createClip(ForgeConstants.SOUND_DIR + filename); + return AudioClip.createClip(SoundSystem.getSoundDirectory() + filename); } @Override diff --git a/forge-gui-mobile/src/forge/screens/settings/SettingsPage.java b/forge-gui-mobile/src/forge/screens/settings/SettingsPage.java index fca57671c7b..f7977786886 100644 --- a/forge-gui-mobile/src/forge/screens/settings/SettingsPage.java +++ b/forge-gui-mobile/src/forge/screens/settings/SettingsPage.java @@ -612,6 +612,17 @@ public class SettingsPage extends TabPage { localizer.getMessage("nlVibrateAfterLongPress")), 6); //Sound Options + lstSettings.addItem(new CustomSelectSetting(FPref.UI_CURRENT_SOUND_SET, + localizer.getMessage("cbpSoundSets"), + localizer.getMessage("nlpSoundSets"), + SoundSystem.getAvailableSoundSets()) { + @Override + public void valueChanged(String newValue) { + super.valueChanged(newValue); + SoundSystem.invalidateSoundCache(); + } + }, + 7); lstSettings.addItem(new CustomSelectSetting(FPref.UI_VOL_SOUNDS, localizer.getMessage("cbAdjustSoundsVolume"), localizer.getMessage("nlAdjustSoundsVolume"), diff --git a/forge-gui/release-files/ANNOUNCEMENTS.txt b/forge-gui/release-files/ANNOUNCEMENTS.txt index 1c2ae20dfd8..5c3b0b31366 100644 --- a/forge-gui/release-files/ANNOUNCEMENTS.txt +++ b/forge-gui/release-files/ANNOUNCEMENTS.txt @@ -1,6 +1,6 @@ #Add one announcement per line Get in the discord if you aren't yet. https://discord.gg/3v9JCVr -The Throne of Eldraine quest world is the largest and most meticulously developed quest world to date, combining fascinating fairytales and interesting interactions. If you've never baked a pie into a pie, have you really played Magic? -Innistrad: Midnight Hunt (MID) and Innistrad: Midnight Hunt Commander (MIC) are 100% supported -Various improvements have been made to the desktop and mobile user interface to enhance the experience +Planar Conquest now features a new plane - Forgotten Realms, based on the AFR and AFC sets. +Forge now supports 100% of core and expansion set cards from Limited Edition Alpha to Innistrad: Midnight Hunt. This includes Equinox, Chaos Orb, and Falling Star. +Sound sets are now configurable - you can place your sound sets under "sound" in your Forge cache (as subfolders) and then select them with the "Sound set" option. *** Android 7 & 8 support is now deprecated. Support will be dropped in an upcoming release. *** diff --git a/forge-gui/res/languages/de-DE.properties b/forge-gui/res/languages/de-DE.properties index 31cb928ee23..04e44b97f85 100644 --- a/forge-gui/res/languages/de-DE.properties +++ b/forge-gui/res/languages/de-DE.properties @@ -111,6 +111,7 @@ cbpGameLogEntryType=Spielberichtsumfang cbpCloseAction=Beenden cbpDefaultFontSize=Standard Schriftgröße cbpCardArtFormat=Kartenbildformat +cbpSoundSets=Sound Set cbpAiProfiles=KI Persönlichkeit cbpStackAdditions=Nachricht bei Stapeländerung cbpDisplayCurrentCardColors=Zeige detaillierte Kartenfarben @@ -141,6 +142,7 @@ nlCompactMainMenu=Aktiviere, um im Seitenmenü platzsparend immer nur eine Menü nlUseSentry=Aktiviere, um automatische Fehlerberichte an die Entwickler zu senden. GamePlay=Spiel nlpMulliganRule=Wähle die Version der Mulligan Regel +nlpSoundSets=Choose the sound set from the ones present in the "sound" folder in your Forge cache directory nlpAiProfiles=Wähle die Spielweise deines KI-Gegners. nlpStackAdditions=Wähle, wann du über Änderungen am Stapel benachrichtigt werden möchtest: Niemals, immer oder nur für durch andere Spieler ausgelöste Effekte und Fähigkeiten nlAnte=Entscheidet, ob um einen Einsatz (Ante) gespielt wird. diff --git a/forge-gui/res/languages/en-US.properties b/forge-gui/res/languages/en-US.properties index c22b8e08639..50d4eb03552 100644 --- a/forge-gui/res/languages/en-US.properties +++ b/forge-gui/res/languages/en-US.properties @@ -113,6 +113,7 @@ cbpCloseAction=Close Action cbpDefaultFontSize=Default Font Size cbpCardArtFormat=Card Art Format cbpAiProfiles=AI Personality +cbpSoundSets=Sound Set cbpStackAdditions=Stack effect notifications cbpDisplayCurrentCardColors=Show Detailed Card Color cbpAutoYieldMode=Auto-Yield @@ -142,6 +143,7 @@ nlCompactMainMenu=Enable for a space efficient sidebar that displays only one me nlUseSentry=When enabled, automatically submits bug reports to developers. GamePlay=Gameplay nlpMulliganRule=Choose the version of the Mulligan rule +nlpSoundSets=Choose the sound set from the ones present in the "sound" folder in your Forge cache directory nlpAiProfiles=Choose your AI opponent nlpStackAdditions=Choose when you want to get visual notifications for an effect added to the stack: Never, always, or only for the effects cast/activated by a AI player or triggered by any player nlAnte=Determines whether or not the game is played for ante. diff --git a/forge-gui/res/languages/es-ES.properties b/forge-gui/res/languages/es-ES.properties index 8ea46c95fe9..6660d6da765 100644 --- a/forge-gui/res/languages/es-ES.properties +++ b/forge-gui/res/languages/es-ES.properties @@ -112,6 +112,7 @@ cbpGameLogEntryType=Registro del juego cbpCloseAction=Acción al cerrar cbpDefaultFontSize=Tamaño de fuente predeterminado cbpCardArtFormat=Card Art Format +cbpSoundSets=Sound Set cbpAiProfiles=Personalidad de la IA cbpStackAdditions=Efecto de la pila de notificaciones cbpDisplayCurrentCardColors=Mostrar color de la carta @@ -142,6 +143,7 @@ nlCompactMainMenu=Habilitar para una barra lateral eficiente en espacio que mues nlUseSentry=Cuando está habilitado, envía automáticamente informes de errores a los desarrolladores. GamePlay=Juego nlpMulliganRule=Elige versión de reglas de mulligan +nlpSoundSets=Choose the sound set from the ones present in the "sound" folder in your Forge cache directory nlpAiProfiles=Elige tu oponente de la IA nlpStackAdditions=Elige cuándo quieres recibir notificaciones visuales para un efecto añadido a la pila: Nunca, siempre o sólo para los efectos lanzados/activados por un jugador IA o activados por cualquier jugador nlAnte=Determina si el juego se juega con apuesta o no. diff --git a/forge-gui/res/languages/it-IT.properties b/forge-gui/res/languages/it-IT.properties index c089272c174..b2361fa154d 100644 --- a/forge-gui/res/languages/it-IT.properties +++ b/forge-gui/res/languages/it-IT.properties @@ -111,6 +111,7 @@ cbpGameLogEntryType=Verbosità del registro di gioco cbpCloseAction=Chiudi cbpDefaultFontSize=Dimensione carattere predefinita cbpCardArtFormat=Formato dell'Illustrazione delle carte +cbpSoundSets=Sound Set cbpAiProfiles=Personalità dell''IA cbpStackAdditions=Notifiche degli effetti in pila cbpDisplayCurrentCardColors=Mostra colore scheda dettagliato @@ -141,6 +142,7 @@ nlCompactMainMenu=Abilitare per una barra laterale efficiente in termini di spaz nlUseSentry=Se abilitato, invia automaticamente segnalazioni di bug agli sviluppatori. GamePlay=Gameplay nlpMulliganRule=Scegli il tipo di Mulligan +nlpSoundSets=Choose the sound set from the ones present in the "sound" folder in your Forge cache directory nlpAiProfiles=Scegli il tuo avversario (IA) nlpStackAdditions=Scegli quando vuoi ricevere una notifica visiva di un effetto aggiunto alla pila: Mai, sempre, o solo per gli effetti lanciati/attivati da un giocatore IA o innescati da un qualsiasi giocatore nlAnte=Determina se l''incontro è giocato o meno con la posta. diff --git a/forge-gui/res/languages/ja-JP.properties b/forge-gui/res/languages/ja-JP.properties index ec920c6c054..8cf6a18b242 100644 --- a/forge-gui/res/languages/ja-JP.properties +++ b/forge-gui/res/languages/ja-JP.properties @@ -112,6 +112,7 @@ cbpGameLogEntryType=ゲームログの詳細レベル cbpCloseAction=閉じる時の動作 cbpDefaultFontSize=デフォルトのフォントサイズ cbpCardArtFormat=カードのアートフォーマット +cbpSoundSets=Sound Set cbpAiProfiles=AI の性格 cbpStackAdditions=スタック効果通知 cbpDisplayCurrentCardColors=詳細なカードの色を表示 @@ -142,6 +143,7 @@ nlCompactMainMenu=サイドバーに同時に一つのメニューグループ nlUseSentry=有効にすると、バグレポートが開発者に自動的に送信されます。 GamePlay=ゲーム設定 nlpMulliganRule=マリガンルールを選択する。 +nlpSoundSets=Choose the sound set from the ones present in the "sound" folder in your Forge cache directory nlpAiProfiles=対戦相手 AI の性格を選択する。 nlpStackAdditions=スタックに追加された能力の視覚通知をいつ取得するかを選択します:(Never[しない]、Always[常時]、またはAIプレーヤーによってキャスト/起動された能力、または任意のプレーヤーによって誘発された能力に対してのみ nlAnte=ゲームでアンティ(賭け)ルールを適用するか選択する。 diff --git a/forge-gui/res/languages/zh-CN.properties b/forge-gui/res/languages/zh-CN.properties index 77ea552a0be..304b7d07bd1 100644 --- a/forge-gui/res/languages/zh-CN.properties +++ b/forge-gui/res/languages/zh-CN.properties @@ -112,6 +112,7 @@ cbpGameLogEntryType=游戏日志详细程度 cbpCloseAction=关闭动作 cbpDefaultFontSize=默认字体大小 cbpCardArtFormat=牌张插画格式 +cbpSoundSets=Sound Set cbpAiProfiles=AI强度 cbpStackAdditions=堆叠效应通知 cbpDisplayCurrentCardColors=显示卡牌颜色详情 @@ -142,6 +143,7 @@ nlCompactMainMenu=启用节省空间的侧边栏,一次只显示一个菜单 nlUseSentry=启用后,会自动向开发人员提交错误报告。 GamePlay=游戏 nlpMulliganRule=选择调度规则 +nlpSoundSets=Choose the sound set from the ones present in the "sound" folder in your Forge cache directory nlpAiProfiles=选择你的AI对手 nlpStackAdditions=选择何时因效应进入堆叠而提供视觉提醒:从不,总是,任何由AI释放或起动以及由任何玩家触发的效应。 nlAnte=确定游戏是否使用赌注 diff --git a/forge-gui/src/main/java/forge/localinstance/properties/ForgeConstants.java b/forge-gui/src/main/java/forge/localinstance/properties/ForgeConstants.java index 792074b7a8d..aeb1bd369f5 100644 --- a/forge-gui/src/main/java/forge/localinstance/properties/ForgeConstants.java +++ b/forge-gui/src/main/java/forge/localinstance/properties/ForgeConstants.java @@ -283,6 +283,7 @@ public final class ForgeConstants { public static final String DB_DIR = CACHE_DIR + "db" + PATH_SEPARATOR; public static final String FONTS_DIR = CACHE_DIR + "fonts" + PATH_SEPARATOR; public static final String CACHE_SKINS_DIR = CACHE_DIR + "skins" + PATH_SEPARATOR; + public static final String CACHE_SOUND_DIR = CACHE_DIR + "sound" + PATH_SEPARATOR; public static final String CACHE_TOKEN_PICS_DIR = PICS_DIR + "tokens" + PATH_SEPARATOR; public static final String CACHE_ICON_PICS_DIR = PICS_DIR + "icons" + PATH_SEPARATOR; public static final String CACHE_SYMBOLS_DIR = PICS_DIR + "symbols" + PATH_SEPARATOR; diff --git a/forge-gui/src/main/java/forge/localinstance/properties/ForgePreferences.java b/forge-gui/src/main/java/forge/localinstance/properties/ForgePreferences.java index 24def10e2a2..354458da64b 100644 --- a/forge-gui/src/main/java/forge/localinstance/properties/ForgePreferences.java +++ b/forge-gui/src/main/java/forge/localinstance/properties/ForgePreferences.java @@ -121,6 +121,7 @@ public class ForgePreferences extends PreferencesStore { UI_VOL_SOUNDS ("100"), UI_VOL_MUSIC ("100"), UI_ALT_SOUND_SYSTEM ("false"), + UI_CURRENT_SOUND_SET("Default"), UI_CURRENT_AI_PROFILE ("Default"), UI_CLONE_MODE_SOURCE ("false"), UI_MATCH_IMAGE_VISIBLE ("true"), diff --git a/forge-gui/src/main/java/forge/sound/EventVisualizer.java b/forge-gui/src/main/java/forge/sound/EventVisualizer.java index 884a8871127..6d9730e1593 100644 --- a/forge-gui/src/main/java/forge/sound/EventVisualizer.java +++ b/forge-gui/src/main/java/forge/sound/EventVisualizer.java @@ -39,7 +39,6 @@ import forge.gui.events.IUiEventVisitor; import forge.gui.events.UiEventAttackerDeclared; import forge.gui.events.UiEventBlockerAssigned; import forge.gui.events.UiEventNextGameDecision; -import forge.localinstance.properties.ForgeConstants; import forge.util.TextUtil; import forge.util.maps.MapOfLists; @@ -331,7 +330,7 @@ public class EventVisualizer extends IGameEventVisitor.Base imp } // Only proceed if the file actually exists - return new File(ForgeConstants.SOUND_DIR, effect).exists(); + return new File(SoundSystem.getSoundDirectory(), effect).exists(); } diff --git a/forge-gui/src/main/java/forge/sound/SoundSystem.java b/forge-gui/src/main/java/forge/sound/SoundSystem.java index d1dcab487c0..63600ef363d 100644 --- a/forge-gui/src/main/java/forge/sound/SoundSystem.java +++ b/forge-gui/src/main/java/forge/sound/SoundSystem.java @@ -1,8 +1,7 @@ package forge.sound; -import java.util.EnumMap; -import java.util.HashMap; -import java.util.Map; +import java.io.File; +import java.util.*; import com.google.common.eventbus.Subscribe; @@ -108,7 +107,7 @@ public class SoundSystem { */ public void play(final String resourceFileName, final boolean isSynchronized) { if (isUsingAltSystem()) { - GuiBase.getInterface().startAltSoundSystem(ForgeConstants.SOUND_DIR + resourceFileName, isSynchronized); + GuiBase.getInterface().startAltSoundSystem(getSoundDirectory() + resourceFileName, isSynchronized); } else { final IAudioClip snd = fetchResource(resourceFileName); @@ -123,7 +122,7 @@ public class SoundSystem { */ public void play(final SoundEffectType type, final boolean isSynchronized) { if (isUsingAltSystem()) { - GuiBase.getInterface().startAltSoundSystem(ForgeConstants.SOUND_DIR + type.getResourceFileName(), isSynchronized); + GuiBase.getInterface().startAltSoundSystem(getSoundDirectory() + type.getResourceFileName(), isSynchronized); } else { final IAudioClip snd = fetchResource(type); if (!isSynchronized || snd.isDone()) { @@ -245,4 +244,44 @@ public class SoundSystem { currentTrack = null; } } + + public static String[] getAvailableSoundSets() + { + final List availableSets = new ArrayList<>(); + + final File dir = new File(ForgeConstants.CACHE_SOUND_DIR); + if (dir != null && dir.exists()) { + final String[] files = dir.list(); + for (String fileName : files) { + String fullPath = ForgeConstants.CACHE_SOUND_DIR + fileName; + if (!fileName.equals("Default") && new File(fullPath).isDirectory()) { + availableSets.add(fileName); + } + } + } + + Collections.sort(availableSets); + availableSets.add(0, "Default"); + + if (availableSets.size() == 1) { + // Default profile only - ensure that the preference is set accordingly + FModel.getPreferences().setPref(FPref.UI_CURRENT_SOUND_SET, "Default"); + } + + return availableSets.toArray(new String[availableSets.size()]); + } + + public static String getSoundDirectory() { + String profileName = FModel.getPreferences().getPref(FPref.UI_CURRENT_SOUND_SET); + if (profileName.equals("Default")) { + return ForgeConstants.SOUND_DIR; + } else { + return ForgeConstants.CACHE_SOUND_DIR + profileName + ForgeConstants.PATH_SEPARATOR; + } + } + + public static void invalidateSoundCache() { + loadedClips.clear(); + loadedScriptClips.clear(); + } }