Feature/network improvements (#7365)

* Initial commit of network improvements
Seperated server properties into their own file, this will eventually help facilitate a headless server.
Fixed the localhost ip mapping to give the correct IP Address instead of failing and defaulting to "localhost"
Fixed UPnP as well as added some additional options and choices regarding UPnP
Added localization strings to all language files. (Translators will need to translate these, but the current English string is there for easy reference so they dont have to search the en-US file)

* Initial commit of network improvements
Seperated server properties into their own file, this will eventually help facilitate a headless server.
Fixed the localhost ip mapping to give the correct IP Address instead of failing and defaulting to "localhost"
Fixed UPnP as well as added some additional options and choices regarding UPnP
Added localization strings to all language files. (Translators will need to translate these, but the current English string is there for easy reference so they dont have to search the en-US file)

* Fixed properties file reference

* Refactored server address parsing logic to use the Java URI class for improved readability and robustness.
Extracted reusable code into separate functions to enhance modularity and maintainability.
General code cleanup to improve structure and readability.

* Fixed a potential issue if a protocol was already specified in the connection url.

* Removed logger implementation as changing loggers is out of scope for this PR
Reverted to JUPnP as its implementation is fixed in #7367
Made some of the new localization strings generic as they can be used elsewhere
Added a server.preferences.example file
removed the server port from the old location (forge.progile.properties.example)
Added a server port back into ForgeConstants as it doesnt make sense for the prefered hosting port of the user to override the default Forge connection port.

* resolve conflicts between this branch and master

* Implemented a parent class for all preference Enums so they can be passed into a function regardless of type using IPref, necessary since I separated server settings into its own FNetPref file
Added server preferences section to the preferences Desktop GUI
Added a port preference setting and a UPnP preference setting to the aforementioned server preferences section
Added a localizedComboBox and localizedComboBoxListener so that localized strings can be used in combobox dropdowns in the server preferences section.

TODO: (In scope)
The new server preferences section needs to be added to Android and IOS perhaps?

TODO: (out of scope)
GamePlayerUtil has a bunch on non localized english strings that should be converted to localized

* Fixed unused import

* Resolved merge conflicts
Added server settings to the reset to defaults function
This commit is contained in:
matthias8422
2025-04-15 17:32:32 -07:00
committed by GitHub
parent e7becacd57
commit 366ed643a7
26 changed files with 557 additions and 95 deletions

View File

@@ -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<Pair<JCheckBox, FPref>> 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<String, String> 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<String> panel = this.view.getCbpServerUPnPOption();
final FComboBox<String> 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 <E> FComboBox<E> createComboBox(final E[] items, final ForgePreferences.FPref setting) {
private <E> FComboBox<E> createComboBox(final E[] items, final PreferencesStore.IPref setting) {
final FComboBox<E> comboBox = new FComboBox<>(items);
addComboBoxListener(comboBox, setting);
return comboBox;
}
private <E> void addComboBoxListener(final FComboBox<E> comboBox, final ForgePreferences.FPref setting) {
private <E> FComboBox<E> createLocalizedComboBox(
final E[] localizedItems,
final PreferencesStore.IPref setting,
final Map<E, String> mapping) {
//Step 1: Create the combo box
final FComboBox<E> 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 <E> void addComboBoxListener(final FComboBox<E> 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 <E> void addLocalizedComboBoxListener(
final FComboBox<E> comboBox,
final PreferencesStore.IPref setting,
final Map<E, String> 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();
};
}
}

View File

@@ -60,6 +60,7 @@ public enum VSubmenuPreferences implements IVSubmenu<CSubmenuPreferences> {
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<CSubmenuPreferences> {
private final FComboBoxPanel<String> cbpDefaultLanguage = new FComboBoxPanel<>(localizer.getMessage("cbpSelectLanguage")+":");
private final FComboBoxPanel<String> cbpAutoUpdater = new FComboBoxPanel<>(localizer.getMessage("cbpAutoUpdater")+":");
private final FComboBoxPanel<String> cbpSwitchStates = new FComboBoxPanel<>(localizer.getMessage("cbpSwitchStates")+":");
private final FComboBoxPanel<String> cbpServerUPnPOption = new FComboBoxPanel<>(localizer.getMessage("cbpServerUPnPOption")+":");
/**
* Constructor.
@@ -281,6 +283,15 @@ public enum VSubmenuPreferences implements IVSubmenu<CSubmenuPreferences> {
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<CSubmenuPreferences> {
}
}
//Server Preference Panel Components
//###################################################################
public final FComboBoxPanel<String> getCbpServerUPnPOption() {
return cbpServerUPnPOption;
}
public FLabel getBtnServerPort() {
return btnServerPort;
}
//###################################################################
public final FComboBoxPanel<String> getCbpAutoUpdater() {
return cbpAutoUpdater;
}
@@ -1053,4 +1075,13 @@ public enum VSubmenuPreferences implements IVSubmenu<CSubmenuPreferences> {
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;
}
}

View File

@@ -85,7 +85,3 @@ decksDir=
# The default value (for all plaforms) is:
# <the decksDir defined above>/constructed/
decksConstructedDir=
# Forge server port. Values under 1024 won't work on Mac OSX or on the various
# *nixes.
serverPort=

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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=チーム数が足りない!チームの配分を調整してください。

View File

@@ -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.

View File

@@ -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=没有足够的团队!请调整团队分配。

View File

@@ -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

View File

@@ -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...

View File

@@ -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();
}
}

View File

@@ -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<ILobbyListener> 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();
}
}

View File

@@ -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<Channel, RemoteClient> 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<InetAddress> 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<InetAddress> 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();
}
}

View File

@@ -27,7 +27,7 @@ public class ConquestPreferences extends PreferencesStore<ConquestPreferences.CQ
/**
* Preference identifiers, and their default values.
*/
public enum CQPref {
public enum CQPref implements IPref{
CURRENT_CONQUEST("DEFAULT"),
AETHER_BASE_DUPLICATE_VALUE("100"),
@@ -61,6 +61,7 @@ public class ConquestPreferences extends PreferencesStore<ConquestPreferences.CQ
this.strDefaultVal = s0;
}
@Override
public String getDefault() {
return this.strDefaultVal;
}

View File

@@ -30,7 +30,7 @@ public class QuestPreferences extends PreferencesStore<QuestPreferences.QPref> 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<QuestPreferences.QPref> i
*
* @return {@link java.lang.String}
*/
@Override
public String getDefault() {
return this.strDefaultVal;
}

View File

@@ -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<String, String> 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<String, String> 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");

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
package forge.localinstance.properties;
public class ForgeNetPreferences extends PreferencesStore<ForgeNetPreferences.FNetPref> {
/**
* 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();
}
}

View File

@@ -25,7 +25,7 @@ public class ForgePreferences extends PreferencesStore<ForgePreferences.FPref> {
/**
* 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<ForgePreferences.FPref> {
this.strDefaultVal = s0;
}
@Override
public String getDefault() {
return strDefaultVal;
}

View File

@@ -43,7 +43,6 @@ public class ForgeProfileProperties {
private static Map<String, String> 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<String, String> 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());
}

View File

@@ -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<T extends Enum<T>> {
public abstract class PreferencesStore<T extends Enum<T> & PreferencesStore.IPref> {
private final Map<T, String> preferenceValues;
private final String filename;
@@ -174,4 +174,8 @@ public abstract class PreferencesStore<T extends Enum<T>> {
else if (gameType.equals("Archenemy Rumble"))
result.add(GameType.ArchenemyRumble);
}
public interface IPref {
String getDefault(); // Common method for getting the default value
}
}

View File

@@ -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<GameType, AchievementCollection> 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

View File

@@ -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;
}
}

View File

@@ -1,18 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="ALL">
<Configuration status="WARN">
<!-- Define Appenders -->
<Appenders>
<Console name="A1">
<PatternLayout pattern="%-4r [%t] %-5p %c %x - %m%n" />
<!-- Console Appender -->
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout>
<Pattern>%d{yyyy-MM-dd HH:mm:ss} [%t] %-5level %logger{36} - %msg%n</Pattern>
</PatternLayout>
</Console>
<!--Sentry name="Sentry" /-->
<!-- File Appender with Rolling Policy -->
<RollingFile name="FileLogger" fileName="logs/app.log" filePattern="logs/app-%d{yyyy-MM-dd}-%i.log.gz">
<PatternLayout>
<Pattern>%d{yyyy-MM-dd HH:mm:ss} [%t] %-5level %logger{36} - %msg%n</Pattern>
</PatternLayout>
<Policies>
<!-- Roll over when the file size reaches 10 MB -->
<SizeBasedTriggeringPolicy size="10MB"/>
</Policies>
<DefaultRolloverStrategy max="10"/>
</RollingFile>
</Appenders>
<!-- Define Loggers -->
<Loggers>
<Root level="ALL">
<AppenderRef ref="A1" />
<!-- Note that the Sentry logging threshold is overridden to the WARN level -->
<!--AppenderRef ref="Sentry" level="WARN" /-->
<!-- Root Logger -->
<Root level="info">
<AppenderRef ref="Console"/>
<AppenderRef ref="FileLogger"/>
</Root>
</Loggers>
</Configuration>
</Configuration>