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 16a86fb388a..7c7c95db026 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 @@ -13,8 +13,10 @@ import forge.gui.UiCommand; import forge.gui.framework.FScreen; import forge.gui.framework.ICDoc; import forge.localinstance.properties.ForgeConstants; +import forge.localinstance.properties.ForgeNetPreferences; import forge.localinstance.properties.ForgePreferences; import forge.localinstance.properties.ForgePreferences.FPref; +import forge.localinstance.properties.PreferencesStore; import forge.model.FModel; import forge.player.GamePlayerUtil; import forge.screens.deckeditor.CDeckEditorUI; @@ -35,6 +37,7 @@ import java.awt.event.ItemEvent; import java.io.File; import java.util.ArrayList; import java.util.List; +import java.util.Map; /** * Controls the preferences submenu in the home UI. @@ -50,6 +53,7 @@ public enum CSubmenuPreferences implements ICDoc { private VSubmenuPreferences view; private ForgePreferences prefs; + private ForgeNetPreferences netPrefs; private boolean updating; private final List> lstControls = new ArrayList<>(); @@ -67,6 +71,7 @@ public enum CSubmenuPreferences implements ICDoc { this.view = VSubmenuPreferences.SINGLETON_INSTANCE; this.prefs = FModel.getPreferences(); + this.netPrefs = FModel.getNetPreferences(); // This updates variable right now and is not standard view.getCbDevMode().addItemListener(arg0 -> { @@ -206,6 +211,7 @@ public enum CSubmenuPreferences implements ICDoc { initializeCardArtFormatComboBox(); initializeCardArtPreference(); initializeAutoUpdaterComboBox(); + initializeServerUPnPComboBox(); initializeMulliganRuleComboBox(); initializeAiProfilesComboBox(); initializeAiSideboardingModeComboBox(); @@ -221,6 +227,7 @@ public enum CSubmenuPreferences implements ICDoc { initializeCounterDisplayLocationComboBox(); initializeGraveyardOrderingComboBox(); initializePlayerNameButton(); + initializeServerPortButton(); initializeDefaultLanguageComboBox(); disableLazyLoading(); @@ -256,7 +263,9 @@ public enum CSubmenuPreferences implements ICDoc { if (FOptionPane.showConfirmDialog(userPrompt, localizer.getMessage("TresetForgeSettingsToDefault"))) { final ForgePreferences prefs = FModel.getPreferences(); prefs.reset(); + netPrefs.reset(); prefs.save(); + netPrefs.save(); update(); Singletons.getControl().restartForge(); } @@ -371,6 +380,31 @@ public enum CSubmenuPreferences implements ICDoc { panel.setComboBox(comboBox, selectedItem); } + private void initializeServerUPnPComboBox() { + // Step 1: Define the localized strings and mappings + final Map upnpPreferenceMapping = ForgeConstants.getUPnPPreferenceMapping(); + final String[] localizedOptions = upnpPreferenceMapping.keySet().toArray(new String[0]); // Localized strings + + // Step 2: Get the preference key + final ForgeNetPreferences.FNetPref uPnPPreference = ForgeNetPreferences.FNetPref.UPnP; + + // Step 3: Create the combo box with localized strings + final FComboBoxPanel panel = this.view.getCbpServerUPnPOption(); + final FComboBox comboBox = createLocalizedComboBox(localizedOptions, uPnPPreference, upnpPreferenceMapping); + + // Step 4: Pre-select the localized value based on the saved internal value + final String savedInternalValue = this.netPrefs.getPref(uPnPPreference); + final String selectedLocalizedValue = upnpPreferenceMapping.entrySet() + .stream() + .filter(entry -> entry.getValue().equals(savedInternalValue)) + .map(Map.Entry::getKey) + .findFirst() + .orElse(localizer.getMessage("lblAsk")); // Default value + + panel.setComboBox(comboBox, selectedLocalizedValue); + } + + private void initializeMulliganRuleComboBox() { final String [] choices = MulliganDefs.getMulliganRuleNames(); final FPref userSetting = FPref.MULLIGAN_RULE; @@ -578,20 +612,74 @@ public enum CSubmenuPreferences implements ICDoc { } - private FComboBox createComboBox(final E[] items, final ForgePreferences.FPref setting) { + private FComboBox createComboBox(final E[] items, final PreferencesStore.IPref setting) { final FComboBox comboBox = new FComboBox<>(items); addComboBoxListener(comboBox, setting); return comboBox; } - private void addComboBoxListener(final FComboBox comboBox, final ForgePreferences.FPref setting) { + private FComboBox createLocalizedComboBox( + final E[] localizedItems, + final PreferencesStore.IPref setting, + final Map mapping) { + + //Step 1: Create the combo box + final FComboBox comboBox = new FComboBox<>(localizedItems); + + //Step 2: Add a listener using the localized to internal mappings to save internal values based on localized selection + addLocalizedComboBoxListener(comboBox, setting, mapping); + + return comboBox; + } + + + private void addComboBoxListener(final FComboBox comboBox, final PreferencesStore.IPref setting) { comboBox.addItemListener(e -> { final E selectedType = comboBox.getSelectedItem(); - CSubmenuPreferences.this.prefs.setPref(setting, selectedType.toString()); + if (setting instanceof ForgePreferences.FPref) { + // Cast setting to ForgePreferences.FPref + CSubmenuPreferences.this.prefs.setPref((ForgePreferences.FPref) setting, selectedType.toString()); + } else if (setting instanceof ForgeNetPreferences.FNetPref) { + // Cast setting to ForgeNetPreferences.FNetPref + CSubmenuPreferences.this.netPrefs.setPref((ForgeNetPreferences.FNetPref) setting, selectedType.toString()); + } CSubmenuPreferences.this.prefs.save(); }); } + private void addLocalizedComboBoxListener( + final FComboBox comboBox, + final PreferencesStore.IPref setting, + final Map mapping) { + + comboBox.addItemListener(e -> { + if (e.getStateChange() == ItemEvent.SELECTED) { + final E selectedLocalized = comboBox.getSelectedItem(); // Localized string + final String internalValue = mapping.get(selectedLocalized); // Map localized string to internal value + + // Save the mapped internal value to the correct preferences + if (setting instanceof ForgePreferences.FPref) { + CSubmenuPreferences.this.prefs.setPref((ForgePreferences.FPref) setting, internalValue); + CSubmenuPreferences.this.prefs.save(); + } else if (setting instanceof ForgeNetPreferences.FNetPref) { + CSubmenuPreferences.this.netPrefs.setPref((ForgeNetPreferences.FNetPref) setting, internalValue); + CSubmenuPreferences.this.netPrefs.save(); + } + } + }); + } + + private void initializeServerPortButton() { + final FLabel btn = view.getBtnServerPort(); + setServerPortButtonText(); + btn.setCommand(getServerPortButtonCommand()); + } + private void setServerPortButtonText() { + final FLabel btn = view.getBtnServerPort(); + final int port = netPrefs.getPrefInt(ForgeNetPreferences.FNetPref.NET_PORT); + btn.setText(Integer.toString(port)); + } + private void initializePlayerNameButton() { final FLabel btn = view.getBtnPlayerName(); setPlayerNameButtonText(); @@ -617,4 +705,11 @@ public enum CSubmenuPreferences implements ICDoc { setPlayerNameButtonText(); }; } + + private UiCommand getServerPortButtonCommand() { + return () -> { + GamePlayerUtil.setServerPort(); + setServerPortButtonText(); + }; + } } 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 7f29d633074..5f4c8910bb0 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 @@ -60,6 +60,7 @@ public enum VSubmenuPreferences implements IVSubmenu { private final FLabel btnTokenPreviewer = new FLabel.Builder().opaque(true).hoverable(true).text(localizer.getMessage("btnTokenPreviewer")).build(); private final FLabel btnPlayerName = new FLabel.Builder().opaque(true).hoverable(true).text("").build(); + private final FLabel btnServerPort = new FLabel.Builder().opaque(true).hoverable(true).text("").build(); private final JCheckBox cbRemoveSmall = new OptionsCheckBox(localizer.getMessage("cbRemoveSmall")); private final JCheckBox cbCardBased = new OptionsCheckBox(localizer.getMessage("cbCardBased")); @@ -146,6 +147,7 @@ public enum VSubmenuPreferences implements IVSubmenu { private final FComboBoxPanel cbpDefaultLanguage = new FComboBoxPanel<>(localizer.getMessage("cbpSelectLanguage")+":"); private final FComboBoxPanel cbpAutoUpdater = new FComboBoxPanel<>(localizer.getMessage("cbpAutoUpdater")+":"); private final FComboBoxPanel cbpSwitchStates = new FComboBoxPanel<>(localizer.getMessage("cbpSwitchStates")+":"); + private final FComboBoxPanel cbpServerUPnPOption = new FComboBoxPanel<>(localizer.getMessage("cbpServerUPnPOption")+":"); /** * Constructor. @@ -281,6 +283,15 @@ public enum VSubmenuPreferences implements IVSubmenu { pnlPrefs.add(cbpAutoYieldMode, comboBoxConstraints); pnlPrefs.add(new NoteLabel(localizer.getMessage("nlpAutoYieldMode")), descriptionConstraints); + //Server Preferences + pnlPrefs.add(new SectionLabel(localizer.getMessage("ServerPreferences")), sectionConstraints); + + pnlPrefs.add(cbpServerUPnPOption, comboBoxConstraints); + pnlPrefs.add(new NoteLabel(localizer.getMessage("nlServerUPnPOptions")), descriptionConstraints); + + pnlPrefs.add(getServerPortPanel(), titleConstraints + ", h 26px!"); + pnlPrefs.add(new NoteLabel(localizer.getMessage("nlServerPort")), descriptionConstraints); + // Deck building options pnlPrefs.add(new SectionLabel(localizer.getMessage("RandomDeckGeneration")), sectionConstraints); @@ -619,6 +630,17 @@ public enum VSubmenuPreferences implements IVSubmenu { } } + //Server Preference Panel Components + //################################################################### + public final FComboBoxPanel getCbpServerUPnPOption() { + return cbpServerUPnPOption; + } + + public FLabel getBtnServerPort() { + return btnServerPort; + } + //################################################################### + public final FComboBoxPanel getCbpAutoUpdater() { return cbpAutoUpdater; } @@ -1053,4 +1075,13 @@ public enum VSubmenuPreferences implements IVSubmenu { p.add(btnPlayerName, "aligny top, h 100%, w 200px!"); return p; } + + private JPanel getServerPortPanel() { + JPanel p = new JPanel(new MigLayout("insets 0, gap 0!")); + p.setOpaque(false); + FLabel lbl = new FLabel.Builder().text(localizer.getMessage("lblServerPort") +": ").fontSize(12).fontStyle(Font.BOLD).build(); + p.add(lbl, "aligny top, h 100%, gap 4px 0 0 0"); + p.add(btnServerPort, "aligny top, h 100%, w 200px!"); + return p; + } } diff --git a/forge-gui/forge.profile.properties.example b/forge-gui/forge.profile.properties.example index 05b4fc72b05..8a6acb2bacb 100644 --- a/forge-gui/forge.profile.properties.example +++ b/forge-gui/forge.profile.properties.example @@ -85,7 +85,3 @@ decksDir= # The default value (for all plaforms) is: # /constructed/ decksConstructedDir= - -# Forge server port. Values under 1024 won't work on Mac OSX or on the various -# *nixes. -serverPort= diff --git a/forge-gui/res/languages/de-DE.properties b/forge-gui/res/languages/de-DE.properties index c3b21b35802..3259f6db1a6 100644 --- a/forge-gui/res/languages/de-DE.properties +++ b/forge-gui/res/languages/de-DE.properties @@ -53,7 +53,9 @@ btnClearImageCache=Leere Bildspeicher btnTokenPreviewer=Spielstein-Vorschau btnCopyToClipboard=In Zwischenablage kopieren cbpAutoUpdater=Auto-Updater +cbpServerUPnPOption=Server UPnP nlAutoUpdater=Wähle aus welcher Quelle Forge aktualisiert wird +nlServerUPnPOptions=Determines if Forge should use UPnP to attempt to automatically open the server port when hosting. cbpSelectLanguage=Sprache nlSelectLanguage=Wähle Sprache (Ist noch in Arbeit und nur teilweise umgesetzt.) (Neustart ist erforderlich.) cbRemoveSmall=Entferne kleine Kreaturen @@ -141,6 +143,9 @@ latestArtOpt=Neueste Version originalArtOpt=Originalbild Troubleshooting=Fehlerbehebung GeneralConfiguration=Allgemeine Einstellungen +ServerPreferences=Server Preferences +lblServerPort=Server Port +nlServerPort=Sets the port number that Forge will host a server on. lblPlayerName=Spielername nlPlayerName=Name unter welchem du beim Spielen geführt wirst. nlCompactMainMenu=Aktiviere, um im Seitenmenü platzsparend immer nur eine Menügruppe anzeigen zu lassen. (Erfordert Neustart) @@ -322,7 +327,11 @@ lblRemove=Entferne ttlblAvatar=L-Klick: Wähle Avatar. R-Klick: Zufälliger Avatar. lblReady=Fertig lblKick=Rauswerfen -lblReallyKick=%s wirklich rauswerfen? +lblReallyKick=%s wirklich rauswerfen?\ +#GamePlayerUtil.java +sOPServerPromptMessage=Please enter a preferred hosting port +sOPServerPromptTitle =Server Hosting Port +sOPServerPromptError=Invalid port number: {0} \n Port number needs to be a number between 1 and 65535 #ForgeMenu.java lblRestart=Neustart lblExit=Beenden @@ -2940,6 +2949,15 @@ lblForgeUnableDetermineYourExternalIP=Forge konnte dein externe IP nicht ermitte lblServerURL=Server-URL lblYourConnectionToHostWasInterrupted=Deine Verbindung mit dem Host ({0}) wurde unterbrochen. lblConnectedIPPort=Verbunden mit {0}:{1} +#FServerManager.java +lblUPnPTitle=UPnP option +lblUPnPQuestion=Attempt to open port {0} automatically? +lblUPnPFailed=UPnP failed to open port {0}. You will need to manually create a port forward for external clients to connect. +lblAlways=Always +lblNotNow=Not Now +lblNever=Never +lblAsk=Ask +lblJustOnce=Just Once #GameLobby.java lblRequiredLeastTwoPlayerStartGame=Es braucht mindestens zwei Spieler für ein Spiel. lblNotEnoughTeams=Nicht genug Teams! Bitte Teamzuordung anpassen. diff --git a/forge-gui/res/languages/en-US.properties b/forge-gui/res/languages/en-US.properties index 529e61558f0..731850f000e 100644 --- a/forge-gui/res/languages/en-US.properties +++ b/forge-gui/res/languages/en-US.properties @@ -53,7 +53,9 @@ btnClearImageCache=Clear Image Cache btnTokenPreviewer=Token Previewer btnCopyToClipboard=Copy to Clipboard cbpAutoUpdater=Auto updater +cbpServerUPnPOption=Server UPnP nlAutoUpdater=Select the release channel to use for updating Forge +nlServerUPnPOptions=Determines if Forge should use UPnP to attempt to automatically open the server port when hosting. cbpSelectLanguage=Language nlSelectLanguage=Select Language (Excluded Game part. Still a work in progress) (REQUIRES RESTART) cbRemoveSmall=Remove Small Creatures @@ -142,6 +144,9 @@ latestArtOpt=Latest Art originalArtOpt=Original Art Troubleshooting=Troubleshooting GeneralConfiguration=General Configuration +ServerPreferences=Server Preferences +lblServerPort=Server Port +nlServerPort=Sets the port number that Forge will host a server on. lblPlayerName=Player Name nlPlayerName=Sets the name that you will be referred to by Forge during gameplay. nlCompactMainMenu=Enable for a space efficient sidebar that displays only one menu group at a time (REQUIRES RESTART). @@ -325,6 +330,10 @@ ttlblAvatar=L-click: Select avatar. R-click: Randomize avatar. lblReady=Ready lblKick=Kick lblReallyKick=Really Kick %s? +#GamePlayerUtil.java +sOPServerPromptMessage=Please enter a preferred hosting port +sOPServerPromptTitle =Server Hosting Port +sOPServerPromptError=Invalid port number: {0} \n Port number needs to be a number between 1 and 65535 #ForgeMenu.java lblRestart=Restart lblExit=Exit @@ -2985,6 +2994,15 @@ lblForgeUnableDetermineYourExternalIP=Forge was unable to determine your externa lblServerURL=Server URL lblYourConnectionToHostWasInterrupted=Your connection to the host ({0}) was interrupted. lblConnectedIPPort=Connected to {0}:{1} +#FServerManager.java +lblUPnPTitle=UPnP option +lblUPnPQuestion=Attempt to open port {0} automatically? +lblUPnPFailed=UPnP failed to open port {0}. You will need to manually create a port forward for external clients to connect. +lblAlways=Always +lblNotNow=Not Now +lblNever=Never +lblAsk=Ask +lblJustOnce=Just Once #GameLobby.java lblRequiredLeastTwoPlayerStartGame=At least two players are required to start a game. lblNotEnoughTeams=There are not enough teams! Please adjust team allocations. diff --git a/forge-gui/res/languages/es-ES.properties b/forge-gui/res/languages/es-ES.properties index f1cbd18a3c8..9a09fe4c670 100644 --- a/forge-gui/res/languages/es-ES.properties +++ b/forge-gui/res/languages/es-ES.properties @@ -53,7 +53,9 @@ btnClearImageCache=Limpiar caché de imágenes btnTokenPreviewer=Previsualizador de fichas (tokens) btnCopyToClipboard=Copiar al portapapeles cbpAutoUpdater=Actualizar Forge +cbpServerUPnPOption=Server UPnP nlAutoUpdater=Selecciona la versión a utilizar para actualizar Forge +nlServerUPnPOptions=Determines if Forge should use UPnP to attempt to automatically open the server port when hosting. cbpSelectLanguage=Idioma nlSelectLanguage=Seleccionar idioma (excepto textos en partida). Todavía un trabajo en progreso (Es necesario reiniciar Forge) cbRemoveSmall=Eliminar criaturas pequeñas @@ -142,6 +144,9 @@ latestArtOpt=Ilustración más reciente originalArtOpt=Ilustración original Troubleshooting=Solución de problemas GeneralConfiguration=Configuración general +ServerPreferences=Server Preferences +lblServerPort=Server Port +nlServerPort=Sets the port number that Forge will host a server on. lblPlayerName=Nombre Jugador nlPlayerName=Establece el nombre al que te referirá Forge durante el juego. nlCompactMainMenu=Habilitar para una barra lateral eficiente en espacio que muestre solo un grupo de menús a la vez (REQUIERE REINICIAR). @@ -325,6 +330,10 @@ ttlblAvatar=Click izdo: Seleccionar avatar. Click dcho: aleatorizar avatar. lblReady=Listo lblKick=Quitar lblReallyKick=¿Quitar a %s? +#GamePlayerUtil.java +sOPServerPromptMessage=Please enter a preferred hosting port +sOPServerPromptTitle =Server Hosting Port +sOPServerPromptError=Invalid port number: {0} \n Port number needs to be a number between 1 and 65535 #ForgeMenu.java lblRestart=Reiniciar lblExit=Salir @@ -2954,6 +2963,15 @@ lblForgeUnableDetermineYourExternalIP=¡Forge no pudo determinar su IP externa!\ lblServerURL=URL del servidor lblYourConnectionToHostWasInterrupted=Tu conexión con el host ({0}) fue interrumpida. lblConnectedIPPort=Conectado a {0}: {1} +#FServerManager.java +lblUPnPTitle=UPnP option +lblUPnPQuestion=Attempt to open port {0} automatically? +lblUPnPFailed=UPnP failed to open port {0}. You will need to manually create a port forward for external clients to connect. +lblAlways=Always +lblNotNow=Not Now +lblNever=Never +lblAsk=Ask +lblJustOnce=Just Once #GameLobby.java lblRequiredLeastTwoPlayerStartGame=Se requieren al menos dos jugadores para comenzar un juego. lblNotEnoughTeams=¡No hay suficientes equipos! Por favor, ajusta las asignaciones del equipo. diff --git a/forge-gui/res/languages/fr-FR.properties b/forge-gui/res/languages/fr-FR.properties index 1c0dcf5dd1d..0e172852fb8 100644 --- a/forge-gui/res/languages/fr-FR.properties +++ b/forge-gui/res/languages/fr-FR.properties @@ -53,7 +53,9 @@ btnClearImageCache=Effacer le cache des images btnTokenPreviewer=Aperçu des jetons btnCopyToClipboard=Copier dans le presse-papiers cbpAutoUpdater=Mise à jour automatique +cbpServerUPnPOption=Server UPnP nlAutoUpdater=Sélectionnez le canal de publication à utiliser pour mettre à jour Forge +nlServerUPnPOptions=Determines if Forge should use UPnP to attempt to automatically open the server port when hosting. cbpSelectLanguage=Langue nlSelectLanguage=Sélectionner la langue (partie du jeu exclue. Encore un travail en cours) (NÉCESSITE UN REDÉMARRAGE) cbRemoveSmall=Supprimer les petites créatures @@ -141,6 +143,9 @@ latestArtOpt=Dernier art originalArtOpt=Art original Troubleshooting=Dépannage GeneralConfiguration=Configuration générale +ServerPreferences=Server Preferences +lblServerPort=Server Port +nlServerPort=Sets the port number that Forge will host a server on. lblPlayerName=Nom du joueur nlPlayerName=Définit le nom auquel vous serez référé par Forge pendant le jeu. nlCompactMainMenu=Activer pour une barre latérale peu encombrante qui affiche un seul groupe de menus à la fois (NÉCESSITE UN REDÉMARRAGE). @@ -323,6 +328,10 @@ ttlblAvatar=L-clic : sélectionnez l'avatar. R-clic : randomiser l'avatar. lblReady=Prêt lblKick=Coup de pied lblReallyKick=Vraiment botter %s ? +#GamePlayerUtil.java +sOPServerPromptMessage=Please enter a preferred hosting port +sOPServerPromptTitle =Server Hosting Port +sOPServerPromptError=Invalid port number: {0} \n Port number needs to be a number between 1 and 65535 #ForgeMenu.java lblRestart=Redémarrer lblExit=Quitter @@ -2948,6 +2957,15 @@ lblForgeUnableDetermineYourExternalIP=Forge n'a pas pu déterminer votre adresse lblServerURL=URL du serveur lblYourConnectionToHostWasInterrupted=Votre connexion à l'hôte ({0}) a été interrompue. lblConnectedIPPort=Connecté à {0} :{1} +#FServerManager.java +lblUPnPTitle=UPnP option +lblUPnPQuestion=Attempt to open port {0} automatically? +lblUPnPFailed=UPnP failed to open port {0}. You will need to manually create a port forward for external clients to connect. +lblAlways=Always +lblNotNow=Not Now +lblNever=Never +lblAsk=Ask +lblJustOnce=Just Once #GameLobby.java lblRequiredLeastTwoPlayerStartGame=Au moins deux joueurs sont requis pour démarrer une partie. lblNotEnoughTeams=Il n'y a pas assez d'équipes ! Veuillez ajuster les allocations d'équipe. diff --git a/forge-gui/res/languages/it-IT.properties b/forge-gui/res/languages/it-IT.properties index e906d49a8b2..6e03ba9cf7d 100644 --- a/forge-gui/res/languages/it-IT.properties +++ b/forge-gui/res/languages/it-IT.properties @@ -52,7 +52,9 @@ btnClearImageCache=Cancella cache immagini btnTokenPreviewer=Visualizzatore di pedine btnCopyToClipboard=Copia negli appunti cbpAutoUpdater=Auto updater +cbpServerUPnPOption=Server UPnP nlAutoUpdater=Seleziona il canale di distribuzione da usare per aggiornare Forge +nlServerUPnPOptions=Determines if Forge should use UPnP to attempt to automatically open the server port when hosting. cbpSelectLanguage=Lingua nlSelectLanguage=Seleziona la lingua (parte di gioco esclusa. Ancora in fase di sviluppo) (RIAVVIO NECESSARIO) cbRemoveSmall=Rimuovi le creature piccole @@ -140,6 +142,9 @@ latestArtOpt=Illustrazione più recente originalArtOpt=Illustrazione originale Troubleshooting=Risoluzione dei problemi GeneralConfiguration=Configurazione generale +ServerPreferences=Server Preferences +lblServerPort=Server Port +nlServerPort=Sets the port number that Forge will host a server on. lblPlayerName=Nome del giocatore nlPlayerName=Imposta il nome che userai in Forge durante il gioco. nlCompactMainMenu=Abilitare per una barra laterale efficiente in termini di spazio che visualizza solo un gruppo di menu alla volta (RIAVVIO RICHIESTO). @@ -322,6 +327,10 @@ ttlblAvatar=L-clic: seleziona avatar. R-clic: avatar casuale. lblReady=Pronto lblKick=Scaccia lblReallyKick=Vuoi davvero scacciare %s? +#GamePlayerUtil.java +sOPServerPromptMessage=Please enter a preferred hosting port +sOPServerPromptTitle =Server Hosting Port +sOPServerPromptError=Invalid port number: {0} \n Port number needs to be a number between 1 and 65535 #ForgeMenu.java lblRestart=Ricomincia lblExit=Esci @@ -2946,6 +2955,15 @@ lblForgeUnableDetermineYourExternalIP=Forge non è riuscito a determinare il tuo lblServerURL=URL del server lblYourConnectionToHostWasInterrupted=La tua connessione all'host ({0}) si è interrotta. lblConnectedIPPort=Connesso a {0}:{1} +#FServerManager.java +lblUPnPTitle=UPnP option +lblUPnPQuestion=Attempt to open port {0} automatically? +lblUPnPFailed=UPnP failed to open port {0}. You will need to manually create a port forward for external clients to connect. +lblAlways=Always +lblNotNow=Not Now +lblNever=Never +lblAsk=Ask +lblJustOnce=Just Once #GameLobby.java lblRequiredLeastTwoPlayerStartGame=Sono richiesti almeno due giocatori per iniziare una partita. lblNotEnoughTeams=Non ci sono abbastanza squadre! Correggi la ripartizione delle squadre. diff --git a/forge-gui/res/languages/ja-JP.properties b/forge-gui/res/languages/ja-JP.properties index f389c3564b2..0ba38f6c389 100644 --- a/forge-gui/res/languages/ja-JP.properties +++ b/forge-gui/res/languages/ja-JP.properties @@ -53,7 +53,9 @@ btnClearImageCache=画像キャッシュのクリア btnTokenPreviewer=トークンを見る btnCopyToClipboard=クリップボードにコピー cbpAutoUpdater=自動アップデート +cbpServerUPnPOptions=Server UPnP nlAutoUpdater=Forge を更新する時に使用するリリースチャネルを選択 +nlServerUPnPOptions=Determines if Forge should use UPnP to attempt to automatically open the server port when hosting. cbpSelectLanguage=言語 nlSelectLanguage=言語の選択(対戦画面はまだ開発中)(再起動が必要) cbRemoveSmall=小さなクリーチャーを除外 @@ -141,6 +143,9 @@ latestArtOpt=最新のアート originalArtOpt=オリジナルアート Troubleshooting=トラブルシューティング GeneralConfiguration=一般設定 +ServerPreferences=Server Preferences +lblServerPort=Server Port +nlServerPort=Sets the port number that Forge will host a server on. lblPlayerName=プレイヤー名 nlPlayerName=ゲームプレイ中に Forge が参照する名前を設定します。 nlCompactMainMenu=サイドバーに同時に一つのメニューグループだけを表示し、省スペースモードを有効にします。(再起動が必要) @@ -323,6 +328,10 @@ ttlblAvatar=左クリック:アバターを選択します。 右クリック lblReady=準備完了 lblKick=キック lblReallyKick=本当に%sをキックしますか? +#GamePlayerUtil.java +sOPServerPromptMessage=Please enter a preferred hosting port +sOPServerPromptTitle =Server Hosting Port +sOPServerPromptError=Invalid port number: {0} \n Port number needs to be a number between 1 and 65535 #ForgeMenu.java lblRestart=リスタート lblExit=終了 @@ -2942,6 +2951,15 @@ lblForgeUnableDetermineYourExternalIP=Forge があなたの外部 IP を決め lblServerURL=サーバー URL lblYourConnectionToHostWasInterrupted=ホスト({0})へのコネクションが中断されました。 lblConnectedIPPort={0}:{1}に繋ぎました +#FServerManager.java +lblUPnPTitle=UPnP option +lblUPnPQuestion=Attempt to open port {0} automatically? +lblUPnPFailed=UPnP failed to open port {0}. You will need to manually create a port forward for external clients to connect. +lblAlways=Always +lblNotNow=Not Now +lblNever=Never +lblAsk=Ask +lblJustOnce=Just Once #GameLobby.java lblRequiredLeastTwoPlayerStartGame=少なくとも二人以上のプレイヤーがないとゲームを始められません。 lblNotEnoughTeams=チーム数が足りない!チームの配分を調整してください。 diff --git a/forge-gui/res/languages/pt-BR.properties b/forge-gui/res/languages/pt-BR.properties index 88cd0fb5849..5bc4b09ab91 100644 --- a/forge-gui/res/languages/pt-BR.properties +++ b/forge-gui/res/languages/pt-BR.properties @@ -54,7 +54,9 @@ btnClearImageCache=Limpar Cache de Imagens btnTokenPreviewer=Pré-visualizador de Ficha btnCopyToClipboard=Copiar para Área de Transferência cbpAutoUpdater=Atualização automática +cbpServerUPnPOption=Server UPnP nlAutoUpdater=Selecione o canal de lançamento para atualizar o Forge +nlServerUPnPOptions=Determines if Forge should use UPnP to attempt to automatically open the server port when hosting. cbpSelectLanguage=Idioma nlSelectLanguage=Selecionar Idioma (Exceto no Jogo. Tradução em andamento) (É NECESSÁRIO REINICIAR) cbRemoveSmall=Remover Criaturas Pequenas @@ -142,6 +144,9 @@ latestArtOpt=Arte mais recente originalArtOpt=Arte Original Troubleshooting=Solução de Problemas GeneralConfiguration=Configuração Geral +ServerPreferences=Server Preferences +lblServerPort=Server Port +nlServerPort=Sets the port number that Forge will host a server on. lblPlayerName=Nome do Jogador nlPlayerName=Defina o seu nome que será utilizado pelo Forge durante o jogo. nlCompactMainMenu=Ative para uma barra lateral eficiente de espaço que exibe apenas um grupo de menu de cada vez (REQUER REINÍCIO). @@ -335,6 +340,10 @@ ttlblAvatar=Click esquerdo\: Selecionar avatar. Click direito\: Avatar aleatóri lblReady=Pronto lblKick=Expulsar lblReallyKick=Quer mesmo expulsar %s? +#GamePlayerUtil.java +sOPServerPromptMessage=Please enter a preferred hosting port +sOPServerPromptTitle =Server Hosting Port +sOPServerPromptError=Invalid port number: {0} \n Port number needs to be a number between 1 and 65535 #ForgeMenu.java lblRestart=Reiniciar lblExit=Sair @@ -3029,6 +3038,15 @@ lblForgeUnableDetermineYourExternalIP=O Forge não pôde determinar seu IP exter lblServerURL=URL do Servidor lblYourConnectionToHostWasInterrupted=Sua conexão com o host ({0}) foi interrompida. lblConnectedIPPort=Conectado em {0}\:{1} +#FServerManager.java +lblUPnPTitle=UPnP option +lblUPnPQuestion=Attempt to open port {0} automatically? +lblUPnPFailed=UPnP failed to open port {0}. You will need to manually create a port forward for external clients to connect. +lblAlways=Always +lblNotNow=Not Now +lblNever=Never +lblAsk=Ask +lblJustOnce=Just Once #GameLobby.java lblRequiredLeastTwoPlayerStartGame=Ao menos dois jogadores são requeridos em um jogo. lblNotEnoughTeams=Não há equipes suficientes\! Ajuste a alocação da equipe. diff --git a/forge-gui/res/languages/zh-CN.properties b/forge-gui/res/languages/zh-CN.properties index bfb8a9fcb1e..23e3c280083 100644 --- a/forge-gui/res/languages/zh-CN.properties +++ b/forge-gui/res/languages/zh-CN.properties @@ -54,7 +54,9 @@ btnTokenPreviewer=衍生物预览器 btnCopyToClipboard=复制到剪切板 cbpSelectLanguage=语言 cbpAutoUpdater=自动更新 +cbpServerUPnPOption=Server UPnP nlAutoUpdater=选择用于更新Forge的发布渠道 +nlServerUPnPOptions=Determines if Forge should use UPnP to attempt to automatically open the server port when hosting. nlSelectLanguage=选择语言(除了正在进行中的游戏)(需要重新启动) cbRemoveSmall=删除小生物 cbCardBased=包括基于单卡生成的套牌 @@ -142,6 +144,9 @@ latestArtOpt=最新艺术 originalArtOpt=原创艺术 Troubleshooting=故障排除 GeneralConfiguration=常规配置 +ServerPreferences=Server Preferences +lblServerPort=Server Port +nlServerPort=Sets the port number that Forge will host a server on. lblPlayerName=玩家名称 nlPlayerName=设置游戏中Forge将引用的名称 nlCompactMainMenu=启用节省空间的侧边栏,一次只显示一个菜单组(需要重新启动)。 @@ -325,6 +330,10 @@ ttlblAvatar=左击:选择头像,右击:随机头像 lblReady=准备好 lblKick=踢 lblReallyKick=准备好踢 %s? +#GamePlayerUtil.java +sOPServerPromptMessage=Please enter a preferred hosting port +sOPServerPromptTitle =Server Hosting Port +sOPServerPromptError=Invalid port number: {0} \n Port number needs to be a number between 1 and 65535 #ForgeMenu.java lblRestart=重启 lblExit=退出 @@ -2932,6 +2941,15 @@ lblForgeUnableDetermineYourExternalIP=Forge无法确定你的公网IP!\n\n{0} lblServerURL=服务器网址 lblYourConnectionToHostWasInterrupted=你与主机的连接断开了({0})。 lblConnectedIPPort=连接到{0}:{1} +#FServerManager.java +lblUPnPTitle=UPnP option +lblUPnPQuestion=Attempt to open port {0} automatically? +lblUPnPFailed=UPnP failed to open port {0}. You will need to manually create a port forward for external clients to connect. +lblAlways=Always +lblNotNow=Not Now +lblNever=Never +lblAsk=Ask +lblJustOnce=Just Once #GameLobby.java lblRequiredLeastTwoPlayerStartGame=至少需要两个牌手才能开始游戏。 lblNotEnoughTeams=没有足够的团队!请调整团队分配。 diff --git a/forge-gui/server.preferences.example b/forge-gui/server.preferences.example new file mode 100644 index 00000000000..aadee932cb0 --- /dev/null +++ b/forge-gui/server.preferences.example @@ -0,0 +1,5 @@ +# Forge server port. Values under 1024 won't work on Mac OSX or on the various +# *nixes. +NET_PORT=36743 +#This determines if Forge should attempt to open a port automatically, on supported routers, using UPnP +UPnP=ASK # ASK, ALWAYS, NEVER \ No newline at end of file diff --git a/forge-gui/src/main/java/forge/gamemodes/net/NetConnectUtil.java b/forge-gui/src/main/java/forge/gamemodes/net/NetConnectUtil.java index cc70f5dbb4b..238aded48dc 100644 --- a/forge-gui/src/main/java/forge/gamemodes/net/NetConnectUtil.java +++ b/forge-gui/src/main/java/forge/gamemodes/net/NetConnectUtil.java @@ -9,6 +9,7 @@ import forge.gamemodes.net.event.MessageEvent; import forge.gamemodes.net.event.NetEvent; import forge.gamemodes.net.server.FServerManager; import forge.gamemodes.net.server.ServerGameLobby; +import forge.localinstance.properties.ForgeNetPreferences; import forge.gui.GuiBase; import forge.gui.interfaces.IGuiGame; import forge.gui.interfaces.ILobbyView; @@ -17,12 +18,13 @@ import forge.interfaces.ILobbyListener; import forge.interfaces.IUpdateable; import forge.localinstance.properties.ForgeConstants; import forge.localinstance.properties.ForgePreferences.FPref; -import forge.localinstance.properties.ForgeProfileProperties; import forge.model.FModel; import forge.player.GamePlayerUtil; import forge.util.Localizer; import org.apache.commons.lang3.StringUtils; +import java.net.URI; + public class NetConnectUtil { private NetConnectUtil() { } @@ -38,7 +40,7 @@ public class NetConnectUtil { } public static ChatMessage host(final IOnlineLobby onlineLobby, final IOnlineChatInterface chatInterface) { - final int port = ForgeProfileProperties.getServerPort(); + final int port = FModel.getNetPreferences().getPrefInt(ForgeNetPreferences.FNetPref.NET_PORT); final FServerManager server = FServerManager.getInstance(); final ServerGameLobby lobby = new ServerGameLobby(); final ILobbyView view = onlineLobby.setLobby(lobby); @@ -100,12 +102,12 @@ public class NetConnectUtil { } public static void copyHostedServerUrl() { - String internalAddress = FServerManager.getInstance().getLocalAddress(); - String externalAddress = FServerManager.getInstance().getExternalAddress(); - String internalUrl = internalAddress + ":" + ForgeProfileProperties.getServerPort(); + String internalAddress = FServerManager.getLocalAddress(); + String externalAddress = FServerManager.getExternalAddress(); + String internalUrl = internalAddress + ":" + FModel.getNetPreferences().getPrefInt(ForgeNetPreferences.FNetPref.NET_PORT); String externalUrl = null; if (externalAddress != null) { - externalUrl = externalAddress + ":" + ForgeProfileProperties.getServerPort(); + externalUrl = externalAddress + ":" + FModel.getNetPreferences().getPrefInt(ForgeNetPreferences.FNetPref.NET_PORT); GuiBase.getInterface().copyToClipboard(externalUrl); } else { GuiBase.getInterface().copyToClipboard(internalAddress); @@ -122,7 +124,24 @@ public class NetConnectUtil { public static ChatMessage join(final String url, final IOnlineLobby onlineLobby, final IOnlineChatInterface chatInterface) { final IGuiGame gui = GuiBase.getInterface().getNewGuiGame(); - final FGameClient client = new FGameClient(FModel.getPreferences().getPref(FPref.PLAYER_NAME), "0", gui); + String hostname; + int port = ForgeConstants.DEFAULT_SERVER_CONNECTION_PORT; + + try { + // Check if the URL already has a protocol + String formattedUrl = url.matches("^[a-zA-Z][a-zA-Z0-9+.-]*://.*") ? url : "http://" + url; + // Parse the URI + URI uri = new URI(formattedUrl); + hostname = uri.getHost(); + if (uri.getPort() != -1) { // If a port is specified in the URL + port = uri.getPort(); + } + } catch (Exception ex) { + hostname = url; // Fallback to original URL if parsing fails + } + + + final FGameClient client = new FGameClient(FModel.getPreferences().getPref(FPref.PLAYER_NAME), "0", gui, hostname, port); onlineLobby.setClient(client); chatInterface.setGameClient(client); final ClientGameLobby lobby = new ClientGameLobby(); @@ -150,22 +169,10 @@ public class NetConnectUtil { }); view.setPlayerChangeListener((index, event) -> client.send(event)); - String hostname = url; - int port = ForgeProfileProperties.getServerPort(); - //see if port specified in URL - int index = url.indexOf(':'); - if (index >= 0) { - hostname = url.substring(0, index); - String portStr = url.substring(index + 1); - try { - port = Integer.parseInt(portStr); - } - catch (Exception ex) {} - } try { - client.connect(hostname, port); + client.connect(); } catch (Exception ex) { //return a message to close the connection so we will not crash... diff --git a/forge-gui/src/main/java/forge/gamemodes/net/ProtocolMethod.java b/forge-gui/src/main/java/forge/gamemodes/net/ProtocolMethod.java index 366f39c5f1f..334bc6acea8 100644 --- a/forge-gui/src/main/java/forge/gamemodes/net/ProtocolMethod.java +++ b/forge-gui/src/main/java/forge/gamemodes/net/ProtocolMethod.java @@ -18,6 +18,7 @@ import forge.trackable.TrackableCollection; import forge.util.ITriggerEvent; import forge.util.ReflectionUtil; + import java.lang.reflect.Method; import java.util.Collection; import java.util.List; @@ -171,6 +172,7 @@ public enum ProtocolMethod { } } } catch (Exception e) { + System.out.println(e.getMessage()); e.printStackTrace(); } } diff --git a/forge-gui/src/main/java/forge/gamemodes/net/client/FGameClient.java b/forge-gui/src/main/java/forge/gamemodes/net/client/FGameClient.java index 59e8c87d0f6..ca7fb579bd7 100644 --- a/forge-gui/src/main/java/forge/gamemodes/net/client/FGameClient.java +++ b/forge-gui/src/main/java/forge/gamemodes/net/client/FGameClient.java @@ -18,18 +18,22 @@ import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.codec.serialization.ClassResolvers; + import java.util.List; import java.util.concurrent.TimeoutException; public class FGameClient implements IToServer { - private final IGuiGame clientGui; + private final String hostname; + private final Integer port; private final List lobbyListeners = Lists.newArrayList(); private final ReplyPool replies = new ReplyPool(); private Channel channel; - public FGameClient(final String username, final String roomKey, final IGuiGame clientGui) { + public FGameClient(String username, String roomKey, IGuiGame clientGui, String hostname, int port) { this.clientGui = clientGui; + this.hostname = hostname; + this.port = port; } final IGuiGame getGui() { @@ -39,7 +43,7 @@ public class FGameClient implements IToServer { return replies; } - public void connect(final String host, final int port) { + public void connect() { final EventLoopGroup group = new NioEventLoopGroup(); try { final Bootstrap b = new Bootstrap() @@ -59,18 +63,20 @@ public class FGameClient implements IToServer { }); // Start the connection attempt. - channel = b.connect(host, port).sync().channel(); + channel = b.connect(this.hostname, this.port).sync().channel(); final ChannelFuture ch = channel.closeFuture(); new Thread(() -> { try { ch.sync(); } catch (final InterruptedException e) { + System.out.println(e.getMessage()); e.printStackTrace(); } finally { group.shutdownGracefully(); } }).start(); } catch (final InterruptedException e) { + System.out.println(e.getMessage()); e.printStackTrace(); } } diff --git a/forge-gui/src/main/java/forge/gamemodes/net/server/FServerManager.java b/forge-gui/src/main/java/forge/gamemodes/net/server/FServerManager.java index 7ae5f89446f..ea4852fc088 100644 --- a/forge-gui/src/main/java/forge/gamemodes/net/server/FServerManager.java +++ b/forge-gui/src/main/java/forge/gamemodes/net/server/FServerManager.java @@ -1,5 +1,6 @@ package forge.gamemodes.net.server; +import com.google.common.collect.ImmutableList; import com.google.common.collect.Maps; import forge.gamemodes.match.LobbySlot; import forge.gamemodes.match.LobbySlotType; @@ -8,9 +9,14 @@ import forge.gamemodes.net.CompatibleObjectEncoder; import forge.gamemodes.net.event.*; import forge.gui.GuiBase; import forge.gui.interfaces.IGuiGame; +import forge.gui.util.SOptionPane; import forge.interfaces.IGameController; import forge.interfaces.ILobbyListener; +import forge.model.FModel; import forge.util.IterableUtil; +import forge.util.Localizer; +import forge.localinstance.properties.ForgeNetPreferences; + import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.*; import io.netty.channel.nio.NioEventLoopGroup; @@ -29,22 +35,21 @@ import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.*; -import java.util.Collection; -import java.util.Collections; -import java.util.Enumeration; -import java.util.Map; +import java.util.*; import java.util.function.Predicate; public final class FServerManager { private static FServerManager instance = null; private final Map clients = Maps.newTreeMap(); - private byte[] externalAddress = new byte[]{8, 8, 8, 8}; private boolean isHosting = false; private EventLoopGroup bossGroup = new NioEventLoopGroup(1); private EventLoopGroup workerGroup = new NioEventLoopGroup(); private UpnpService upnpService = null; private ServerGameLobby localLobby; private ILobbyListener lobbyListener; + private boolean UPnPMapped = false; + private int port; + private static final Localizer localizer = Localizer.getInstance(); private final Thread shutdownHook = new Thread(() -> { if (isHosting()) { stopServer(false); @@ -76,6 +81,15 @@ public final class FServerManager { } public void startServer(final int port) { + this.port = port; + String UPnPOption = FModel.getNetPreferences().getPref(ForgeNetPreferences.FNetPref.UPnP); + boolean startUPnP; + if(UPnPOption.equalsIgnoreCase("ASK")) { + startUPnP = callUPnPDialog(); + } else { + startUPnP = UPnPOption.equalsIgnoreCase("ALWAYS"); + } + System.out.println("Starting Multiplayer Server"); try { final ServerBootstrap b = new ServerBootstrap() .group(bossGroup, workerGroup) @@ -102,19 +116,46 @@ public final class FServerManager { try { ch.sync(); } catch (final InterruptedException e) { + System.out.println(e.getMessage()); e.printStackTrace(); } finally { stopServer(); } }).start(); - mapNatPort(port); + if(startUPnP) { + mapNatPort(); + } Runtime.getRuntime().addShutdownHook(shutdownHook); isHosting = true; } catch (final InterruptedException e) { + System.out.println(e.getMessage()); e.printStackTrace(); } } + private boolean callUPnPDialog() { + switch (SOptionPane.showOptionDialog(localizer.getMessageorUseDefault("lblUPnPQuestion", String.format("Attempt to open port %d automatically?", port), port), + localizer.getMessageorUseDefault("lblUPnPTitle", "UPnP option"), + null, + ImmutableList.of(localizer.getMessageorUseDefault("lblJustOnce", "Just Once"), + localizer.getMessageorUseDefault("lblNotNow", "Not Now"), + localizer.getMessageorUseDefault("lblAlways", "Always"), + localizer.getMessageorUseDefault("lblNever", "Never")), 0)) { + case 2: + FModel.getNetPreferences().setPref(ForgeNetPreferences.FNetPref.UPnP, "ALWAYS"); + FModel.getNetPreferences().save(); + case 0: + //case 2 falls in here + return true; + case 3: + FModel.getNetPreferences().setPref(ForgeNetPreferences.FNetPref.UPnP, "NEVER"); + FModel.getNetPreferences().save(); + default: + //case 1 defaults to here + return false; + } + } + public void stopServer() { stopServer(true); } @@ -213,34 +254,35 @@ public final class FServerManager { // inspired by: // https://stackoverflow.com/a/34873630 // https://stackoverflow.com/a/901943 - private String getRoutableAddress(boolean preferIpv4, boolean preferIPv6) throws SocketException, UnknownHostException { - try (DatagramSocket s = new DatagramSocket()) { - s.connect(InetAddress.getByAddress(this.externalAddress), 0); - NetworkInterface n = NetworkInterface.getByInetAddress(s.getLocalAddress()); - Enumeration en = n.getInetAddresses(); - while (en.hasMoreElements()) { - InetAddress addr = en.nextElement(); - if (addr instanceof Inet4Address) { - if (preferIPv6) { - continue; + private static String getRoutableAddress(boolean preferIpv4, boolean preferIPv6) throws SocketException, UnknownHostException { + try (DatagramSocket socket = new DatagramSocket()) { + // Connect to a well-known external address (Google's public DNS server) + socket.connect(InetAddress.getByName("8.8.8.8"), 12345); // Use a valid port instead of 0 + InetAddress localAddress = socket.getLocalAddress(); + + // Check if the local address belongs to a valid network interface + NetworkInterface networkInterface = NetworkInterface.getByInetAddress(localAddress); + if (networkInterface != null && networkInterface.isUp()) { + Enumeration addresses = networkInterface.getInetAddresses(); + while (addresses.hasMoreElements()) { + InetAddress addr = addresses.nextElement(); + if (addr instanceof Inet4Address && preferIpv4) { + return addr.getHostAddress(); } - return addr.getHostAddress(); - } - if (addr instanceof Inet6Address) { - if (preferIpv4) { - continue; + if (addr instanceof Inet6Address && preferIPv6) { + return addr.getHostAddress(); } - return addr.getHostAddress(); } } } - return null; + return "localhost"; } - public String getLocalAddress() { + public static String getLocalAddress() { try { return getRoutableAddress(true, false); } catch (final Exception e) { + System.out.println(e.getMessage()); e.printStackTrace(); return "localhost"; } @@ -254,12 +296,14 @@ public final class FServerManager { whatismyip.openStream())); return in.readLine(); } catch (IOException e) { + System.out.println(e.getMessage()); e.printStackTrace(); } finally { if (in != null) { try { in.close(); } catch (IOException e) { + System.out.println(e.getMessage()); e.printStackTrace(); } } @@ -267,10 +311,9 @@ public final class FServerManager { return null; } - private void mapNatPort(final int port) { + private void mapNatPort() { final String localAddress = getLocalAddress(); final PortMapping portMapping = new PortMapping(port, localAddress, PortMapping.Protocol.TCP, "Forge"); - // Shutdown existing UPnP service if already running if (upnpService != null) { upnpService.shutdown(); @@ -283,10 +326,10 @@ public final class FServerManager { // Add a PortMappingListener upnpService.getRegistry().addListener(new PortMappingListener(portMapping)); - // Trigger device discovery upnpService.getControlPoint().search(); } catch (Exception e) { + System.out.println(e.getMessage()); e.printStackTrace(); } } diff --git a/forge-gui/src/main/java/forge/gamemodes/planarconquest/ConquestPreferences.java b/forge-gui/src/main/java/forge/gamemodes/planarconquest/ConquestPreferences.java index ba0085c884e..0ea4da28cd7 100644 --- a/forge-gui/src/main/java/forge/gamemodes/planarconquest/ConquestPreferences.java +++ b/forge-gui/src/main/java/forge/gamemodes/planarconquest/ConquestPreferences.java @@ -27,7 +27,7 @@ public class ConquestPreferences extends PreferencesStore i /** * Preference identifiers, and their default values. */ - public enum QPref { + public enum QPref implements IPref{ // if enabled, player must follow world rules in duels (allowed sets only, banned/restricted cards etc.) WORLD_RULES_CONFORMANCE("0"), @@ -211,6 +211,7 @@ public class QuestPreferences extends PreferencesStore i * * @return {@link java.lang.String} */ + @Override public String getDefault() { return this.strDefaultVal; } 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 fdc1026b5ff..783594a2f05 100644 --- a/forge-gui/src/main/java/forge/localinstance/properties/ForgeConstants.java +++ b/forge-gui/src/main/java/forge/localinstance/properties/ForgeConstants.java @@ -19,21 +19,24 @@ package forge.localinstance.properties; import forge.gui.GuiBase; import forge.util.FileUtil; +import forge.util.Localizer; import java.io.File; import java.util.Collections; import java.util.Map; public final class ForgeConstants { - public static final String GITHUB_FORGE_URL = "https://github.com/Card-Forge/forge/"; - public static final String GITHUB_RELEASES_ATOM = GITHUB_FORGE_URL + "releases.atom"; - public static final String GITHUB_COMMITS_ATOM = GITHUB_FORGE_URL + "commits/master.atom"; - public static final String GITHUB_SNAPSHOT_URL = GITHUB_FORGE_URL + "releases/download/daily-snapshots/"; - public static final String RELEASE_URL = "https://releases.cardforge.org/"; - public static final String PATH_SEPARATOR = File.separator; - public static final String ASSETS_DIR = GuiBase.getInterface().getAssetsDir(); - public static final String PROFILE_FILE = ASSETS_DIR + "forge.profile.properties"; - public static final String PROFILE_TEMPLATE_FILE = PROFILE_FILE + ".example"; + private static final Localizer localizer = Localizer.getInstance(); + public static final String GITHUB_FORGE_URL = "https://github.com/Card-Forge/forge/"; + public static final String GITHUB_RELEASES_ATOM = GITHUB_FORGE_URL + "releases.atom"; + public static final String GITHUB_COMMITS_ATOM = GITHUB_FORGE_URL + "commits/master.atom"; + public static final String GITHUB_SNAPSHOT_URL = GITHUB_FORGE_URL + "releases/download/daily-snapshots/"; + public static final String RELEASE_URL = "https://releases.cardforge.org/"; + public static final String PATH_SEPARATOR = File.separator; + public static final String ASSETS_DIR = GuiBase.getInterface().getAssetsDir(); + public static final String PROFILE_FILE = ASSETS_DIR + "forge.profile.properties"; + public static final String PROFILE_TEMPLATE_FILE = PROFILE_FILE + ".example"; + public static final Integer DEFAULT_SERVER_CONNECTION_PORT = 36743; public static final String RES_DIR = ASSETS_DIR + "res" + PATH_SEPARATOR; public static final String ADVENTURE_DIR = RES_DIR + "adventure" + PATH_SEPARATOR; @@ -221,7 +224,6 @@ public final class ForgeConstants { public static final String CACHE_DIR; public static final String CACHE_CARD_PICS_DIR; public static final Map CACHE_CARD_PICS_SUBDIR; - public static final int SERVER_PORT_NUMBER; public static final String DECK_BASE_DIR; public static final String DECK_CONSTRUCTED_DIR; static { @@ -232,7 +234,6 @@ public final class ForgeConstants { CACHE_CARD_PICS_SUBDIR = Collections.unmodifiableMap(ForgeProfileProperties.getCardPicsSubDirs()); DECK_BASE_DIR = ForgeProfileProperties.getDecksDir(); DECK_CONSTRUCTED_DIR = ForgeProfileProperties.getDecksConstructedDir(); - SERVER_PORT_NUMBER = ForgeProfileProperties.getServerPort(); } // data that is only in the profile dirs @@ -266,6 +267,7 @@ public final class ForgeConstants { public static final String DECK_TINY_LEADERS_DIR= DECK_BASE_DIR + "tiny_leaders" + PATH_SEPARATOR; public static final String DECK_BRAWL_DIR = DECK_BASE_DIR + "brawl" + PATH_SEPARATOR; public static final String MAIN_PREFS_FILE = USER_PREFS_DIR + "forge.preferences"; + public static final String SERVER_PREFS_FILE = USER_PREFS_DIR + "server.preferences"; public static final String CARD_PREFS_FILE = USER_PREFS_DIR + "card.preferences"; public static final String DECK_PREFS_FILE = USER_PREFS_DIR + "deck.preferences"; public static final String QUEST_PREFS_FILE = USER_PREFS_DIR + "quest.preferences"; @@ -375,6 +377,14 @@ public final class ForgeConstants { // Supported video mode names and dimensions (currently used in Adventure Mode) public static final String[] VIDEO_MODES = {"720p", "768p", "900p", "1080p", "1440p", "2160p"}; + public static Map getUPnPPreferenceMapping() { + return Map.of( + localizer.getMessage("lblAsk"), "ASK", + localizer.getMessage("lblAlways"), "ALWAYS", + localizer.getMessage("lblNever"), "NEVER" + ); + } + public enum CounterDisplayLocation { TOP("Top of Card"), BOTTOM("Bottom of Card"); diff --git a/forge-gui/src/main/java/forge/localinstance/properties/ForgeNetPreferences.java b/forge-gui/src/main/java/forge/localinstance/properties/ForgeNetPreferences.java new file mode 100644 index 00000000000..917a9c5ff90 --- /dev/null +++ b/forge-gui/src/main/java/forge/localinstance/properties/ForgeNetPreferences.java @@ -0,0 +1,70 @@ +/* + * Forge: Play Magic: the Gathering. + * Copyright (C) 2011 Forge Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package forge.localinstance.properties; + + + +public class ForgeNetPreferences extends PreferencesStore { + + /** + * Preference identifiers and their default values. + */ + public enum FNetPref implements IPref { + NET_PORT("36743"), + UPnP("ASK"); + + private final String strDefaultVal; + + FNetPref(final String s0) { + this.strDefaultVal = s0; + } + + @Override + public String getDefault() { + return strDefaultVal; + } + + + } + + /** Instantiates a ForgePreferences object. */ + public ForgeNetPreferences() { + super(ForgeConstants.SERVER_PREFS_FILE, FNetPref.class); + } + + @Override + protected FNetPref[] getEnumValues() { + return FNetPref.values(); + } + + @Override + protected FNetPref valueOf(final String name) { + try { + return FNetPref.valueOf(name); + } + catch (final Exception e) { + return null; + } + } + + @Override + protected String getPrefDefault(final FNetPref key) { + return key.getDefault(); + } + +} 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 f034c1146ec..3cdf1599436 100644 --- a/forge-gui/src/main/java/forge/localinstance/properties/ForgePreferences.java +++ b/forge-gui/src/main/java/forge/localinstance/properties/ForgePreferences.java @@ -25,7 +25,7 @@ public class ForgePreferences extends PreferencesStore { /** * Preference identifiers and their default values. */ - public enum FPref { + public enum FPref implements IPref { PLAYER_NAME (""), CONSTRUCTED_P1_DECK_STATE(""), CONSTRUCTED_P2_DECK_STATE(""), @@ -290,6 +290,7 @@ public class ForgePreferences extends PreferencesStore { this.strDefaultVal = s0; } + @Override public String getDefault() { return strDefaultVal; } diff --git a/forge-gui/src/main/java/forge/localinstance/properties/ForgeProfileProperties.java b/forge-gui/src/main/java/forge/localinstance/properties/ForgeProfileProperties.java index b28f088201a..6416190bf61 100644 --- a/forge-gui/src/main/java/forge/localinstance/properties/ForgeProfileProperties.java +++ b/forge-gui/src/main/java/forge/localinstance/properties/ForgeProfileProperties.java @@ -43,7 +43,6 @@ public class ForgeProfileProperties { private static Map cardPicsSubDirs; private static String decksDir; private static String decksConstructedDir; - private static int serverPort; private static final String USER_DIR_KEY = "userDir"; private static final String CACHE_DIR_KEY = "cacheDir"; @@ -51,7 +50,7 @@ public class ForgeProfileProperties { private static final String CARD_PICS_SUB_DIRS_KEY = "cardPicsSubDirs"; private static final String DECKS_DIR_KEY = "decksDir"; private static final String DECKS_CONSTRUCTED_DIR_KEY = "decksConstructedDir"; - private static final String SERVER_PORT_KEY = "serverPort"; + private ForgeProfileProperties() { //prevent initializing static class @@ -75,7 +74,7 @@ public class ForgeProfileProperties { cardPicsSubDirs = getMap(props, CARD_PICS_SUB_DIRS_KEY); decksDir = getDir(props, DECKS_DIR_KEY, userDir + "decks" + File.separator); decksConstructedDir = getDir(props, DECKS_CONSTRUCTED_DIR_KEY, decksDir + "constructed" + File.separator); - serverPort = getInt(props, SERVER_PORT_KEY, 36743); // "Forge" using phone keypad + //ensure directories exist FileUtil.ensureDirectoryExists(userDir); @@ -127,10 +126,6 @@ public class ForgeProfileProperties { save(); } - public static int getServerPort() { - return serverPort; - } - private static Map getMap(final Properties props, final String propertyKey) { final String strMap = props.getProperty(propertyKey, "").trim(); return FileSection.parseToMap(strMap, FileSection.ARROW_KV_SEPARATOR); @@ -234,9 +229,6 @@ public class ForgeProfileProperties { } sb.append("\n"); } - if (serverPort != 0) { - sb.append(SERVER_PORT_KEY + "=").append(serverPort); - } if (sb.length() > 0) { FileUtil.writeFile(ForgeConstants.PROFILE_FILE, sb.toString()); } diff --git a/forge-gui/src/main/java/forge/localinstance/properties/PreferencesStore.java b/forge-gui/src/main/java/forge/localinstance/properties/PreferencesStore.java index 8fc414e91d6..461ad6540d2 100644 --- a/forge-gui/src/main/java/forge/localinstance/properties/PreferencesStore.java +++ b/forge-gui/src/main/java/forge/localinstance/properties/PreferencesStore.java @@ -35,7 +35,7 @@ import forge.util.TextUtil; * Loads preferred values when instantiated. * If a requested value is not present, default is returned. */ -public abstract class PreferencesStore> { +public abstract class PreferencesStore & PreferencesStore.IPref> { private final Map preferenceValues; private final String filename; @@ -174,4 +174,8 @@ public abstract class PreferencesStore> { else if (gameType.equals("Archenemy Rumble")) result.add(GameType.ArchenemyRumble); } + + public interface IPref { + String getDefault(); // Common method for getting the default value + } } diff --git a/forge-gui/src/main/java/forge/model/FModel.java b/forge-gui/src/main/java/forge/model/FModel.java index acca808d20e..0e5541b3f23 100644 --- a/forge-gui/src/main/java/forge/model/FModel.java +++ b/forge-gui/src/main/java/forge/model/FModel.java @@ -53,6 +53,7 @@ import forge.item.PaperCardPredicates; import forge.itemmanager.ItemManagerConfig; import forge.localinstance.achievements.*; import forge.localinstance.properties.ForgeConstants; +import forge.localinstance.properties.ForgeNetPreferences; import forge.localinstance.properties.ForgePreferences; import forge.localinstance.properties.ForgePreferences.FPref; import forge.player.GamePlayerUtil; @@ -83,7 +84,7 @@ public final class FModel { private static QuestPreferences questPreferences; private static ConquestPreferences conquestPreferences; private static ForgePreferences preferences; - + private static ForgeNetPreferences netPreferences; private static Map achievements; // Someone should take care of 2 gauntlets here @@ -230,6 +231,7 @@ public final class FModel { } questPreferences = new QuestPreferences(); conquestPreferences = new ConquestPreferences(); + netPreferences = new ForgeNetPreferences(); fantasyBlocks = new StorageBase<>("Custom blocks", new CardBlock.Reader(ForgeConstants.BLOCK_DATA_DIR + "fantasyblocks.txt", magicDb.getEditions())); themedChaosDrafts = new StorageBase<>("Themed Chaos Drafts", new ThemedChaosDraft.Reader(ForgeConstants.BLOCK_DATA_DIR + "chaosdraftthemes.txt")); planes = new StorageBase<>("Conquest planes", new ConquestPlane.Reader(ForgeConstants.CONQUEST_PLANES_DIR + "planes.txt")); @@ -478,6 +480,9 @@ public final class FModel { public static ForgePreferences getPreferences() { return preferences; } + public static ForgeNetPreferences getNetPreferences() { + return netPreferences; + } public static AchievementCollection getAchievements(GameType gameType) { switch (gameType) { //translate gameType to appropriate type if needed diff --git a/forge-gui/src/main/java/forge/player/GamePlayerUtil.java b/forge-gui/src/main/java/forge/player/GamePlayerUtil.java index 09be7b3c4bd..0477bb1959c 100644 --- a/forge-gui/src/main/java/forge/player/GamePlayerUtil.java +++ b/forge-gui/src/main/java/forge/player/GamePlayerUtil.java @@ -6,9 +6,11 @@ import forge.ai.AiProfileUtil; import forge.ai.LobbyPlayerAi; import forge.gui.GuiBase; import forge.gui.util.SOptionPane; +import forge.localinstance.properties.ForgeNetPreferences; import forge.localinstance.properties.ForgePreferences.FPref; import forge.model.FModel; import forge.util.GuiDisplayUtil; +import forge.util.Localizer; import forge.util.MyRandom; import org.apache.commons.lang3.StringUtils; @@ -16,7 +18,7 @@ import java.util.Set; public final class GamePlayerUtil { private GamePlayerUtil() { } - + private static Localizer localizer = Localizer.getInstance(); private static final LobbyPlayer guiPlayer = new LobbyPlayerHuman("Human"); public static LobbyPlayer getGuiPlayer() { return guiPlayer; @@ -117,6 +119,13 @@ public final class GamePlayerUtil { } } + public static void setServerPort() { + final int oldPort = FModel.getNetPreferences().getPrefInt(ForgeNetPreferences.FNetPref.NET_PORT); + int newPort = getServerPortPrompt(oldPort); + FModel.getNetPreferences().setPref(ForgeNetPreferences.FNetPref.NET_PORT, String.valueOf(newPort)); + FModel.getNetPreferences().save(); + } + private static void showThankYouPrompt(final String playerName) { SOptionPane.showMessageDialog("Thank you, " + playerName + ". " + "You will not be prompted again but you can change\n" @@ -140,6 +149,29 @@ public final class GamePlayerUtil { playerName); } + private static Integer getServerPortPrompt(final Integer serverPort) { + String input = SOptionPane.showInputDialog( + localizer.getMessage("sOPServerPromptMessage"), + localizer.getMessage("sOPServerPromptTitle"), + null, + serverPort.toString(), + null, + true + ); + Integer port; + try { + port = Integer.parseInt(input); + } catch (NumberFormatException nfe) { + SOptionPane.showErrorDialog(localizer.getMessage("sOPServerPromptError", input)); + return serverPort; + } + if(port < 0 || port > 65535) { + SOptionPane.showErrorDialog(localizer.getMessage("sOPServerPromptError", input)); + return serverPort; + } + return port; + } + private static String getVerifiedPlayerName(String newName, final String oldName) { if (newName == null || !StringUtils.isAlphanumericSpace(newName)) { newName = (StringUtils.isBlank(oldName) ? "Human" : oldName); @@ -150,4 +182,6 @@ public final class GamePlayerUtil { } return newName; } + + } diff --git a/forge-gui/src/main/resources/log4j2.xml b/forge-gui/src/main/resources/log4j2.xml index c378e95d10f..533cb355e1e 100644 --- a/forge-gui/src/main/resources/log4j2.xml +++ b/forge-gui/src/main/resources/log4j2.xml @@ -1,18 +1,33 @@ - + + - - + + + + %d{yyyy-MM-dd HH:mm:ss} [%t] %-5level %logger{36} - %msg%n + - + + + + %d{yyyy-MM-dd HH:mm:ss} [%t] %-5level %logger{36} - %msg%n + + + + + + + + - - - - + + + + - \ No newline at end of file +