From 9701bfaa5c5ceeb14dc2c51d626debb4a538a72e Mon Sep 17 00:00:00 2001 From: Boondorl Date: Wed, 2 Jul 2025 19:56:17 -0400 Subject: [PATCH] Added multiplayer tab for launcher + backend improvements Adds a multiplayer tab to allow hosting and joining games from within the GZDoom launcher rather than needed to use the command line. This has its own set of defaults independent from the main play page which necessitated rewriting how this information is passed and stored in the backend. A startup info struct is now passed back which has its defaults set from the cvars and then propagates any changes to it back to the defaults after selection is complete, making it much simpler to interface with the engine defaults. --- src/CMakeLists.txt | 1 + src/common/engine/i_interface.cpp | 143 +++++- src/common/engine/i_interface.h | 46 ++ src/common/platform/posix/cocoa/i_system.mm | 15 +- src/common/platform/posix/i_system.h | 3 +- src/common/platform/posix/sdl/i_system.cpp | 14 +- src/common/platform/win32/i_system.cpp | 6 +- src/common/platform/win32/i_system.h | 3 +- src/d_iwad.cpp | 87 +--- src/launcher/launcherbuttonbar.cpp | 9 +- src/launcher/launcherwindow.cpp | 47 +- src/launcher/launcherwindow.h | 14 +- src/launcher/networkpage.cpp | 539 ++++++++++++++++++++ src/launcher/networkpage.h | 112 ++++ src/launcher/playgamepage.cpp | 54 +- src/launcher/playgamepage.h | 11 +- src/launcher/settingspage.cpp | 50 +- src/launcher/settingspage.h | 10 +- 18 files changed, 1007 insertions(+), 157 deletions(-) create mode 100644 src/launcher/networkpage.cpp create mode 100644 src/launcher/networkpage.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ddeba6029..012c1f10e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -976,6 +976,7 @@ set (PCH_SOURCES launcher/launcherbuttonbar.cpp launcher/playgamepage.cpp launcher/settingspage.cpp + launcher/networkpage.cpp common/audio/sound/i_sound.cpp common/audio/sound/oalsound.cpp diff --git a/src/common/engine/i_interface.cpp b/src/common/engine/i_interface.cpp index dfedbe8ee..fabf398d9 100644 --- a/src/common/engine/i_interface.cpp +++ b/src/common/engine/i_interface.cpp @@ -5,6 +5,7 @@ #include "c_cvars.h" #include "gstrings.h" #include "version.h" +#include "m_argv.h" static_assert(sizeof(void*) == 8, "Only LP64/LLP64 builds are officially supported. " @@ -30,11 +31,32 @@ bool pauseext; FStartupInfo GameStartupInfo; -CVAR(Bool, queryiwad, QUERYIWADDEFAULT, CVAR_ARCHIVE | CVAR_GLOBALCONFIG); -CVAR(String, defaultiwad, "", CVAR_ARCHIVE | CVAR_GLOBALCONFIG); CVAR(Bool, vid_fps, false, 0) +CVAR(Bool, queryiwad, QUERYIWADDEFAULT, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) +CVAR(Bool, saveargs, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) +CVAR(Bool, savenetfile, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) +CVAR(Bool, savenetargs, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) +CVAR(String, defaultiwad, "", CVAR_ARCHIVE | CVAR_GLOBALCONFIG) +CVAR(String, defaultargs, "", CVAR_ARCHIVE | CVAR_GLOBALCONFIG) +CVAR(String, defaultnetiwad, "", CVAR_ARCHIVE | CVAR_GLOBALCONFIG) +CVAR(String, defaultnetargs, "", CVAR_ARCHIVE | CVAR_GLOBALCONFIG) +CVAR(Int, defaultnetplayers, 8, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) +CVAR(Int, defaultnethostport, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) +CVAR(Int, defaultnetticdup, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) +CVAR(Int, defaultnetmode, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) +CVAR(Int, defaultnetgamemode, -1, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) +CVAR(Bool, defaultnetaltdm, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) +CVAR(String, defaultnetaddress, "", CVAR_ARCHIVE | CVAR_GLOBALCONFIG) +CVAR(Int, defaultnetjoinport, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) +CVAR(Int, defaultnetpage, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) +CVAR(Int, defaultnethostteam, 255, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) +CVAR(Int, defaultnetjointeam, 255, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) +CVAR(Bool, defaultnetextratic, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) +CVAR(String, defaultnetsavefile, "", CVAR_ARCHIVE | CVAR_GLOBALCONFIG) EXTERN_CVAR(Bool, ui_generic) +EXTERN_CVAR(Int, vid_preferbackend) +EXTERN_CVAR(Bool, vid_fullscreen) CUSTOM_CVAR(String, language, "auto", CVAR_ARCHIVE | CVAR_NOINITCALL | CVAR_GLOBALCONFIG) { @@ -43,3 +65,120 @@ CUSTOM_CVAR(String, language, "auto", CVAR_ARCHIVE | CVAR_NOINITCALL | CVAR_GLOB if (sysCallbacks.LanguageChanged) sysCallbacks.LanguageChanged(self); } +// Some of this info has to be passed and managed from the front end since it's game-engine specific. +FStartupSelectionInfo::FStartupSelectionInfo(const TArray& wads, FArgs& args, int startFlags) : Wads(&wads), Args(&args), DefaultStartFlags(startFlags) +{ + DefaultQueryIWAD = queryiwad; + DefaultLanguage = language; + DefaultBackend = vid_preferbackend; + DefaultFullscreen = vid_fullscreen; + + if (defaultiwad[0] != '\0') + { + for (int i = 0; i < wads.Size(); ++i) + { + if (!wads[i].Name.CompareNoCase(defaultiwad)) + { + DefaultIWAD = i; + break; + } + } + } + DefaultArgs = defaultargs; + bSaveArgs = saveargs; + + if (defaultnetiwad[0] != '\0') + { + for (int i = 0; i < wads.Size(); ++i) + { + if (!wads[i].Name.CompareNoCase(defaultnetiwad)) + { + DefaultNetIWAD = i; + break; + } + } + } + DefaultNetArgs = defaultnetargs; + DefaultNetPage = defaultnetpage; + DefaultNetSaveFile = defaultnetsavefile; + bSaveNetFile = savenetfile; + bSaveNetArgs = savenetargs; + + DefaultNetPlayers = defaultnetplayers; + DefaultNetHostPort = defaultnethostport; + DefaultNetTicDup = defaultnetticdup; + DefaultNetMode = defaultnetmode; + DefaultNetGameMode = defaultnetgamemode; + DefaultNetAltDM = defaultnetaltdm; + DefaultNetHostTeam = defaultnethostteam; + DefaultNetExtraTic = defaultnetextratic; + + DefaultNetAddress = defaultnetaddress; + DefaultNetJoinPort = defaultnetjoinport; + DefaultNetJoinTeam = defaultnetjointeam; +} + +// Return whatever IWAD the user selected. +int FStartupSelectionInfo::SaveInfo() +{ + DefaultLanguage.StripLeftRight(); + + DefaultArgs.StripLeftRight(); + + DefaultNetArgs.StripLeftRight(); + AdditionalNetArgs.StripLeftRight(); + DefaultNetAddress.StripLeftRight(); + DefaultNetSaveFile.StripLeftRight(); + + queryiwad = DefaultQueryIWAD; + language = DefaultLanguage.GetChars(); + vid_fullscreen = DefaultFullscreen; + if (DefaultBackend != vid_preferbackend) + vid_preferbackend = DefaultBackend; + + if (bNetStart) + { + savenetfile = bSaveNetFile; + savenetargs = bSaveNetArgs; + + defaultnetiwad = (*Wads)[DefaultNetIWAD].Name.GetChars(); + defaultnetpage = DefaultNetPage; + defaultnetsavefile = savenetfile ? DefaultNetSaveFile.GetChars() : ""; + defaultnetargs = savenetargs ? DefaultNetArgs.GetChars() : ""; + + if (bHosting) + { + defaultnetplayers = DefaultNetPlayers; + defaultnethostport = DefaultNetHostPort; + defaultnetticdup = DefaultNetTicDup; + defaultnetmode = DefaultNetMode; + defaultnetgamemode = DefaultNetGameMode; + defaultnetaltdm = DefaultNetAltDM; + defaultnethostteam = DefaultNetHostTeam; + defaultnetextratic = DefaultNetExtraTic; + } + else + { + defaultnetaddress = DefaultNetAddress.GetChars(); + defaultnetjoinport = DefaultNetJoinPort; + defaultnetjointeam = DefaultNetJoinTeam; + } + + if (!DefaultNetArgs.IsEmpty()) + Args->AppendArgsString(DefaultNetArgs); + if (!AdditionalNetArgs.IsEmpty()) + Args->AppendArgsString(AdditionalNetArgs); + + return DefaultNetIWAD; + } + + defaultiwad = (*Wads)[DefaultIWAD].Name.GetChars(); + saveargs = bSaveArgs; + defaultargs = saveargs ? DefaultArgs.GetChars() : ""; + + if (!DefaultArgs.IsEmpty()) + Args->AppendArgsString(DefaultArgs); + + return DefaultIWAD; +} + diff --git a/src/common/engine/i_interface.h b/src/common/engine/i_interface.h index 4289bfeab..e901705f2 100644 --- a/src/common/engine/i_interface.h +++ b/src/common/engine/i_interface.h @@ -10,6 +10,7 @@ class FGameTexture; class FTextureID; enum EUpscaleFlags : int; class FConfigFile; +class FArgs; struct FTranslationID; struct SystemCallbacks @@ -58,6 +59,51 @@ struct WadStuff FString Name; }; +struct FStartupSelectionInfo +{ + const TArray* Wads = nullptr; + FArgs* Args = nullptr; + + // Local game info + int DefaultIWAD = 0; + FString DefaultArgs = {}; + bool bSaveArgs = true; + + // Settings + int DefaultStartFlags = 0; + bool DefaultQueryIWAD = true; + FString DefaultLanguage = "auto"; + int DefaultBackend = 1; + bool DefaultFullscreen = true; + + // Net game info + int DefaultNetIWAD = 0; + bool bNetStart = false; + bool bHosting = false; + bool bSaveNetFile = false; + bool bSaveNetArgs = true; + int DefaultNetPage = 0; + FString DefaultNetArgs = {}; + FString AdditionalNetArgs = {}; // These ones shouldn't be saved. + FString DefaultNetSaveFile = {}; + int DefaultNetHostTeam = 255; + int DefaultNetPlayers = 8; + int DefaultNetHostPort = 0; + int DefaultNetTicDup = 0; + bool DefaultNetExtraTic = false; + int DefaultNetMode = -1; + int DefaultNetGameMode = -1; + bool DefaultNetAltDM = false; + + FString DefaultNetAddress = {}; + int DefaultNetJoinPort = 0; + int DefaultNetJoinTeam = 255; + + FStartupSelectionInfo() = delete; + FStartupSelectionInfo(const TArray& wads, FArgs& args, int startFlags); + int SaveInfo(); +}; + extern FString endoomName; extern bool batchrun; diff --git a/src/common/platform/posix/cocoa/i_system.mm b/src/common/platform/posix/cocoa/i_system.mm index 267bac31d..bafd7ab10 100644 --- a/src/common/platform/posix/cocoa/i_system.mm +++ b/src/common/platform/posix/cocoa/i_system.mm @@ -33,6 +33,7 @@ #include "i_common.h" #include "c_cvars.h" +#include "i_interface.h" #include #include @@ -122,21 +123,27 @@ void I_ShowFatalError(const char *message) } -int I_PickIWad(WadStuff* const wads, const int numwads, const bool showwin, const int defaultiwad, int&, FString&) +bool I_PickIWad(bool showwin, FStartupSelectionInfo& info) { if (!showwin) { - return defaultiwad; + return true; } I_SetMainWindowVisible(false); extern int I_PickIWad_Cocoa(WadStuff*, int, bool, int); - const int result = I_PickIWad_Cocoa(wads, numwads, showwin, defaultiwad); + const int result = I_PickIWad_Cocoa(&(*info.Wads)[0], (int)info.Wads->Size(), showwin, info.DefaultIWAD); I_SetMainWindowVisible(true); - return result; + if (result >= 0) + { + info.DefaultIWAD = result; + return true; + } + + return false; } diff --git a/src/common/platform/posix/i_system.h b/src/common/platform/posix/i_system.h index da99cf9e0..4f05ad09a 100644 --- a/src/common/platform/posix/i_system.h +++ b/src/common/platform/posix/i_system.h @@ -14,6 +14,7 @@ #include "zstring.h" struct WadStuff; +struct FStartupSelectionInfo; #ifndef SHARE_DIR #define SHARE_DIR "/usr/local/share/" @@ -37,7 +38,7 @@ void I_PrintStr (const char *str); void I_SetIWADInfo (); // Pick from multiple IWADs to use -int I_PickIWad (WadStuff *wads, int numwads, bool queryiwad, int defaultiwad, int&, FString &); +bool I_PickIWad (bool showwin, FStartupSelectionInfo& info); // [RH] Checks the registry for Steam's install path, so we can scan its // directories for IWADs if the user purchased any through Steam. diff --git a/src/common/platform/posix/sdl/i_system.cpp b/src/common/platform/posix/sdl/i_system.cpp index db793f04c..059123622 100644 --- a/src/common/platform/posix/sdl/i_system.cpp +++ b/src/common/platform/posix/sdl/i_system.cpp @@ -302,17 +302,23 @@ void I_PrintStr(const char *cp) if (StartWindow) RedrawProgressBar(ProgressBarCurPos,ProgressBarMaxPos); } -int I_PickIWad (WadStuff *wads, int numwads, bool showwin, int defaultiwad, int& autoloadflags, FString &extraArgs) +bool I_PickIWad (bool showwin, FStartupSelectionInfo& info) { if (!showwin) { - return defaultiwad; + return true; } #ifdef __APPLE__ - return I_PickIWad_Cocoa (wads, numwads, showwin, defaultiwad); + const int ret = I_PickIWad_Cocoa(&(*info.Wads)[0], (int)info.Wads->Size(), showwin, info.DefaultIWAD); + if (ret >= 0) + { + info.DefaultIWAD = ret; + return true; + } + return false; #else - return LauncherWindow::ExecModal(wads, numwads, defaultiwad, &autoloadflags, &extraArgs); + return LauncherWindow::ExecModal(info); #endif } diff --git a/src/common/platform/win32/i_system.cpp b/src/common/platform/win32/i_system.cpp index c2615d51f..3ce88a38f 100644 --- a/src/common/platform/win32/i_system.cpp +++ b/src/common/platform/win32/i_system.cpp @@ -358,7 +358,7 @@ static void SetQueryIWad(HWND dialog) // //========================================================================== -int I_PickIWad(WadStuff *wads, int numwads, bool showwin, int defaultiwad, int& autoloadflags, FString &extraArgs) +bool I_PickIWad(bool showwin, FStartupSelectionInfo& info) { int vkey; if (stricmp(queryiwad_key, "shift") == 0) @@ -375,9 +375,9 @@ int I_PickIWad(WadStuff *wads, int numwads, bool showwin, int defaultiwad, int& } if (showwin || (vkey != 0 && GetAsyncKeyState(vkey))) { - return LauncherWindow::ExecModal(wads, numwads, defaultiwad, &autoloadflags, &extraArgs); + return LauncherWindow::ExecModal(info); } - return defaultiwad; + return true; } //========================================================================== diff --git a/src/common/platform/win32/i_system.h b/src/common/platform/win32/i_system.h index f83b3f8c1..5bb027e05 100644 --- a/src/common/platform/win32/i_system.h +++ b/src/common/platform/win32/i_system.h @@ -9,6 +9,7 @@ #include "utf8.h" struct WadStuff; +struct FStartupSelectionInfo; // [RH] Detects the OS the game is running under. void I_DetectOS (void); @@ -37,7 +38,7 @@ void I_PrintStr (const char *cp); void I_SetIWADInfo (); // Pick from multiple IWADs to use -int I_PickIWad(WadStuff* wads, int numwads, bool queryiwad, int defaultiwad, int& autoloadflags, FString &extraArgs); +bool I_PickIWad(bool showwin, FStartupSelectionInfo& info); // The ini could not be saved at exit bool I_WriteIniFailed (const char* filename); diff --git a/src/d_iwad.cpp b/src/d_iwad.cpp index 54fba56d7..755dfac3e 100644 --- a/src/d_iwad.cpp +++ b/src/d_iwad.cpp @@ -52,7 +52,6 @@ #include "gstrings.h" EXTERN_CVAR(Bool, queryiwad); -EXTERN_CVAR(String, defaultiwad); EXTERN_CVAR(Bool, disableautoload) EXTERN_CVAR(Bool, autoloadlights) EXTERN_CVAR(Bool, autoloadbrightmaps) @@ -584,8 +583,6 @@ FString FIWadManager::IWADPathFileSearch(const FString &file) return ""; } -CVAR(String, extra_args, "", CVAR_ARCHIVE | CVAR_GLOBALCONFIG); - int FIWadManager::IdentifyVersion (std::vector&wadfiles, const char *iwad, const char *zdoom_wad, const char *optional_wad) { const char *iwadparm = Args->CheckValue ("-iwad"); @@ -759,69 +756,37 @@ int FIWadManager::IdentifyVersion (std::vector&wadfiles, const char // Present the IWAD selection box. bool alwaysshow = (queryiwad && !Args->CheckParm("-iwad") && !foundprio); - if (alwaysshow || picks.Size() > 1) + if (!havepicked && (alwaysshow || picks.Size() > 1)) { - // Locate the user's prefered IWAD, if it was found. - if (defaultiwad[0] != '\0') + TArray wads; + for (auto & found : picks) { - for (unsigned i = 0; i < picks.Size(); ++i) - { - FString &basename = mIWadInfos[picks[i].mInfoIndex].Name; - if (basename.CompareNoCase(defaultiwad) == 0) - { - pick = i; - break; - } - } + WadStuff stuff; + stuff.Name = mIWadInfos[found.mInfoIndex].Name; + stuff.Path = ExtractFileBase(found.mFullPath.GetChars()); + wads.Push(stuff); } - if (alwaysshow || picks.Size() > 1) + + int flags = 0; + if (disableautoload) flags |= 1; + if (autoloadlights) flags |= 2; + if (autoloadbrightmaps) flags |= 4; + if (autoloadwidescreen) flags |= 8; + + FStartupSelectionInfo info = FStartupSelectionInfo(wads, *Args, flags); + if (I_PickIWad(queryiwad, info)) { - if (!havepicked) - { - TArray wads; - for (auto & found : picks) - { - WadStuff stuff; - stuff.Name = mIWadInfos[found.mInfoIndex].Name; - stuff.Path = ExtractFileBase(found.mFullPath.GetChars()); - wads.Push(stuff); - } - int flags = 0;; - - if (disableautoload) flags |= 1; - if (autoloadlights) flags |= 2; - if (autoloadbrightmaps) flags |= 4; - if (autoloadwidescreen) flags |= 8; - - FString extraArgs = *extra_args; - - pick = I_PickIWad(&wads[0], (int)wads.Size(), queryiwad, pick, flags, extraArgs); - if (pick >= 0) - { - extraArgs.StripLeftRight(); - - extra_args = extraArgs.GetChars(); - - if(extraArgs.Len() > 0) - { - Args->AppendArgsString(extraArgs); - } - - disableautoload = !!(flags & 1); - autoloadlights = !!(flags & 2); - autoloadbrightmaps = !!(flags & 4); - autoloadwidescreen = !!(flags & 8); - - // The newly selected IWAD becomes the new default - defaultiwad = mIWadInfos[picks[pick].mInfoIndex].Name.GetChars(); - } - else - { - return -1; - } - havepicked = true; - } + pick = info.SaveInfo(); + disableautoload = !!(info.DefaultStartFlags & 1); + autoloadlights = !!(info.DefaultStartFlags & 2); + autoloadbrightmaps = !!(info.DefaultStartFlags & 4); + autoloadwidescreen = !!(info.DefaultStartFlags & 8); } + else + { + return -1; + } + havepicked = true; } // zdoom.pk3 must always be the first file loaded and the IWAD second. diff --git a/src/launcher/launcherbuttonbar.cpp b/src/launcher/launcherbuttonbar.cpp index 789881000..89e18070d 100644 --- a/src/launcher/launcherbuttonbar.cpp +++ b/src/launcher/launcherbuttonbar.cpp @@ -15,7 +15,14 @@ LauncherButtonbar::LauncherButtonbar(LauncherWindow* parent) : Widget(parent) void LauncherButtonbar::UpdateLanguage() { - PlayButton->SetText(GStrings.GetString("PICKER_PLAY")); + auto launcher = GetLauncher(); + if (!launcher->IsInMultiplayer()) + PlayButton->SetText(GStrings.GetString("PICKER_PLAY")); + else if (launcher->IsHosting()) + PlayButton->SetText(GStrings.GetString("PICKER_PLAYHOST")); + else + PlayButton->SetText(GStrings.GetString("PICKER_PLAYJOIN")); + ExitButton->SetText(GStrings.GetString("PICKER_EXIT")); } diff --git a/src/launcher/launcherwindow.cpp b/src/launcher/launcherwindow.cpp index b60db7cf8..e6ab31839 100644 --- a/src/launcher/launcherwindow.cpp +++ b/src/launcher/launcherwindow.cpp @@ -3,6 +3,7 @@ #include "launcherbuttonbar.h" #include "playgamepage.h" #include "settingspage.h" +#include "networkpage.h" #include "v_video.h" #include "version.h" #include "i_interface.h" @@ -12,25 +13,22 @@ #include #include -int LauncherWindow::ExecModal(WadStuff* wads, int numwads, int defaultiwad, int* autoloadflags, FString * extraArgs) +bool LauncherWindow::ExecModal(FStartupSelectionInfo& info) { Size screenSize = GetScreenSize(); double windowWidth = 615.0; double windowHeight = 700.0; - auto launcher = std::make_unique(wads, numwads, defaultiwad, autoloadflags); + auto launcher = std::make_unique(info); launcher->SetFrameGeometry((screenSize.width - windowWidth) * 0.5, (screenSize.height - windowHeight) * 0.5, windowWidth, windowHeight); - if(extraArgs) launcher->PlayGame->SetExtraArgs(extraArgs->GetChars()); launcher->Show(); DisplayWindow::RunLoop(); - if(extraArgs) *extraArgs = launcher->PlayGame->GetExtraArgs(); - return launcher->ExecResult; } -LauncherWindow::LauncherWindow(WadStuff* wads, int numwads, int defaultiwad, int* autoloadflags) : Widget(nullptr, WidgetType::Window) +LauncherWindow::LauncherWindow(FStartupSelectionInfo& info) : Widget(nullptr, WidgetType::Window), Info(&info) { SetWindowTitle(GAMENAME); @@ -38,11 +36,15 @@ LauncherWindow::LauncherWindow(WadStuff* wads, int numwads, int defaultiwad, int Pages = new TabWidget(this); Buttonbar = new LauncherButtonbar(this); - PlayGame = new PlayGamePage(this, wads, numwads, defaultiwad); - Settings = new SettingsPage(this, autoloadflags); + PlayGame = new PlayGamePage(this, info); + Settings = new SettingsPage(this, info); + Network = new NetworkPage(this, info); Pages->AddTab(PlayGame, "Play"); Pages->AddTab(Settings, "Settings"); + Pages->AddTab(Network, "Multiplayer"); + + Network->InitializeTabs(info); UpdateLanguage(); @@ -50,17 +52,38 @@ LauncherWindow::LauncherWindow(WadStuff* wads, int numwads, int defaultiwad, int PlayGame->SetFocus(); } +void LauncherWindow::UpdatePlayButton() +{ + Buttonbar->UpdateLanguage(); +} + +bool LauncherWindow::IsInMultiplayer() const +{ + return Pages->GetCurrentIndex() >= 0 ? Pages->GetCurrentWidget() == Network : false; +} + +bool LauncherWindow::IsHosting() const +{ + return IsInMultiplayer() && Network->IsInHost(); +} + void LauncherWindow::Start() { - Settings->Save(); + Info->bNetStart = IsInMultiplayer(); - ExecResult = PlayGame->GetSelectedGame(); + Settings->SetValues(*Info); + if (Info->bNetStart) + Network->SetValues(*Info); + else + PlayGame->SetValues(*Info); + + ExecResult = true; DisplayWindow::ExitLoop(); } void LauncherWindow::Exit() { - ExecResult = -1; + ExecResult = false; DisplayWindow::ExitLoop(); } @@ -68,8 +91,10 @@ void LauncherWindow::UpdateLanguage() { Pages->SetTabText(PlayGame, GStrings.GetString("PICKER_TAB_PLAY")); Pages->SetTabText(Settings, GStrings.GetString("OPTMNU_TITLE")); + Pages->SetTabText(Network, "Multiplayer"); PlayGame->UpdateLanguage(); Settings->UpdateLanguage(); + Network->UpdateLanguage(); Buttonbar->UpdateLanguage(); } diff --git a/src/launcher/launcherwindow.h b/src/launcher/launcherwindow.h index 8e7c8bbe6..d10623a36 100644 --- a/src/launcher/launcherwindow.h +++ b/src/launcher/launcherwindow.h @@ -9,18 +9,23 @@ class LauncherBanner; class LauncherButtonbar; class PlayGamePage; class SettingsPage; +class NetworkPage; struct WadStuff; +struct FStartupSelectionInfo; class LauncherWindow : public Widget { public: - static int ExecModal(WadStuff* wads, int numwads, int defaultiwad, int* autoloadflags, FString * extraArgs = nullptr); + static bool ExecModal(FStartupSelectionInfo& info); - LauncherWindow(WadStuff* wads, int numwads, int defaultiwad, int* autoloadflags); + LauncherWindow(FStartupSelectionInfo& info); void UpdateLanguage(); void Start(); void Exit(); + bool IsInMultiplayer() const; + bool IsHosting() const; + void UpdatePlayButton(); private: void OnClose() override; @@ -32,6 +37,9 @@ private: PlayGamePage* PlayGame = nullptr; SettingsPage* Settings = nullptr; + NetworkPage* Network = nullptr; - int ExecResult = -1; + FStartupSelectionInfo* Info = nullptr; + + bool ExecResult = false; }; diff --git a/src/launcher/networkpage.cpp b/src/launcher/networkpage.cpp new file mode 100644 index 000000000..317042ebc --- /dev/null +++ b/src/launcher/networkpage.cpp @@ -0,0 +1,539 @@ + +#include "networkpage.h" +#include "launcherwindow.h" +#include "gstrings.h" +#include "c_cvars.h" +#include "i_net.h" +#include "i_interface.h" +#include +#include +#include +#include +#include +#include +#include + +constexpr double EditHeight = 24.0; + +NetworkPage::NetworkPage(LauncherWindow* launcher, const FStartupSelectionInfo& info) : Widget(nullptr), Launcher(launcher) +{ + ParametersEdit = new LineEdit(this); + ParametersLabel = new TextLabel(this); + SaveFileEdit = new LineEdit(this); + SaveFileLabel = new TextLabel(this); + SaveFileCheckbox = new CheckboxLabel(this); + SaveArgsCheckbox = new CheckboxLabel(this); + IWADsList = new ListView(this); + + SaveFileCheckbox->SetChecked(info.bSaveNetFile); + if (!info.DefaultNetSaveFile.IsEmpty()) + SaveFileEdit->SetText(info.DefaultNetSaveFile.GetChars()); + + SaveArgsCheckbox->SetChecked(info.bSaveNetArgs); + if (!info.DefaultNetArgs.IsEmpty()) + ParametersEdit->SetText(info.DefaultNetArgs.GetChars()); + + StartPages = new TabWidget(this); + HostPage = new HostSubPage(this, info); + JoinPage = new JoinSubPage(this, info); + + for (const auto& wad : *info.Wads) + { + const char* filepart = strrchr(wad.Path.GetChars(), '/'); + if (filepart == nullptr) + filepart = wad.Path.GetChars(); + else + ++filepart; + + FString work; + if (*filepart) + work.Format("%s (%s)", wad.Name.GetChars(), filepart); + else + work = wad.Name.GetChars(); + + IWADsList->AddItem(work.GetChars()); + } + + if (info.DefaultNetIWAD >= 0 && info.DefaultNetIWAD < info.Wads->Size()) + { + IWADsList->SetSelectedItem(info.DefaultNetIWAD); + IWADsList->ScrollToItem(info.DefaultNetIWAD); + } + + IWADsList->OnActivated = [=]() { OnIWADsListActivated(); }; +} + +// This has to be done after the main page is parented, otherwise it won't have the correct +// info to pull from. +void NetworkPage::InitializeTabs(const FStartupSelectionInfo& info) +{ + StartPages->AddTab(HostPage, "Host"); + StartPages->AddTab(JoinPage, "Join"); + + switch (info.DefaultNetPage) + { + case 1: + StartPages->SetCurrentWidget(JoinPage); + break; + default: + StartPages->SetCurrentWidget(HostPage); + break; + } +} + +void NetworkPage::OnIWADsListActivated() +{ + Launcher->Start(); +} + +void NetworkPage::OnSetFocus() +{ + IWADsList->SetFocus(); +} + +void NetworkPage::SetValues(FStartupSelectionInfo& info) const +{ + info.DefaultNetIWAD = IWADsList->GetSelectedItem(); + info.DefaultNetArgs = ParametersEdit->GetText(); + + info.bHosting = IsInHost(); + if (info.bHosting) + { + info.DefaultNetPage = 0; + HostPage->SetValues(info); + } + else + { + info.DefaultNetPage = 1; + JoinPage->SetValues(info); + } + + info.bSaveNetFile = SaveFileCheckbox->GetChecked(); + info.bSaveNetArgs = SaveArgsCheckbox->GetChecked(); + const auto save = SaveFileEdit->GetText(); + if (!save.empty()) + info.AdditionalNetArgs.AppendFormat(" -loadgame %s", save.c_str()); + info.DefaultNetSaveFile = save; +} + +void NetworkPage::UpdatePlayButton() +{ + Launcher->UpdatePlayButton(); +} + +bool NetworkPage::IsInHost() const +{ + return StartPages->GetCurrentIndex() >= 0 ? StartPages->GetCurrentWidget() == HostPage : false; +} + +void NetworkPage::OnGeometryChanged() +{ + const double w = GetWidth(); + const double h = GetHeight(); + + const double wSize = w * 0.45 - 2.5; + + double y = h - SaveArgsCheckbox->GetPreferredHeight(); + SaveArgsCheckbox->SetFrameGeometry(0.0, y, wSize, SaveArgsCheckbox->GetPreferredHeight()); + + y -= SaveFileCheckbox->GetPreferredHeight(); + SaveFileCheckbox->SetFrameGeometry(0.0, y, wSize, SaveFileCheckbox->GetPreferredHeight()); + + y -= EditHeight + 2.0; + ParametersEdit->SetFrameGeometry(0.0, y, wSize, EditHeight); + y -= ParametersLabel->GetPreferredHeight(); + ParametersLabel->SetFrameGeometry(0.0, y, wSize, ParametersLabel->GetPreferredHeight()); + + y -= EditHeight + 2.0; + SaveFileEdit->SetFrameGeometry(0.0, y, wSize, EditHeight); + y -= SaveFileLabel->GetPreferredHeight(); + SaveFileLabel->SetFrameGeometry(0.0, y, wSize, SaveFileLabel->GetPreferredHeight()); + y -= 5.0; + + IWADsList->SetFrameGeometry(0.0, 0.0, wSize, y); + + const double xOfs = w * 0.45 + 2.5; + StartPages->SetFrameGeometry(xOfs, 0.0, w - xOfs, h); +} + +void NetworkPage::UpdateLanguage() +{ + ParametersLabel->SetText(GStrings.GetString("PICKER_ADDPARM")); + SaveFileLabel->SetText(GStrings.GetString("PICKER_LOADSAVE")); + SaveFileCheckbox->SetText(GStrings.GetString("PICKER_REMSAVE")); + SaveArgsCheckbox->SetText(GStrings.GetString("PICKER_REMPARM")); + + StartPages->SetTabText(HostPage, GStrings.GetString("PICKER_HOST")); + StartPages->SetTabText(JoinPage, GStrings.GetString("PICKER_JOIN")); + HostPage->UpdateLanguage(); + JoinPage->UpdateLanguage(); +} + +HostSubPage::HostSubPage(NetworkPage* main, const FStartupSelectionInfo& info) : Widget(nullptr), MainTab(main) +{ + NetModesLabel = new TextLabel(this); + AutoNetmodeCheckbox = new CheckboxLabel(this); + PacketServerCheckbox = new CheckboxLabel(this); + PeerToPeerCheckbox = new CheckboxLabel(this); + + AutoNetmodeCheckbox->SetRadioStyle(true); + PacketServerCheckbox->SetRadioStyle(true); + PeerToPeerCheckbox->SetRadioStyle(true); + AutoNetmodeCheckbox->FuncChanged = [this](bool on) { if (on) { PacketServerCheckbox->SetChecked(false); PeerToPeerCheckbox->SetChecked(false); }}; + PacketServerCheckbox->FuncChanged = [this](bool on) { if (on) { AutoNetmodeCheckbox->SetChecked(false); PeerToPeerCheckbox->SetChecked(false); }}; + PeerToPeerCheckbox->FuncChanged = [this](bool on) { if (on) { PacketServerCheckbox->SetChecked(false); AutoNetmodeCheckbox->SetChecked(false); }}; + + switch (info.DefaultNetMode) + { + case 0: + PeerToPeerCheckbox->SetChecked(true); + break; + case 1: + PacketServerCheckbox->SetChecked(true); + break; + default: + AutoNetmodeCheckbox->SetChecked(true); + break; + } + + TicDupList = new ListView(this); + TicDupLabel = new TextLabel(this); + ExtraTicCheckbox = new CheckboxLabel(this); + + std::vector widths = { 30.0, 30.0 }; + TicDupList->SetColumnWidths(widths); + TicDupList->AddItem("35.0"); + TicDupList->UpdateItem("Hz", 0, 1); + TicDupList->AddItem("17.5"); + TicDupList->UpdateItem("Hz", 1, 1); + TicDupList->AddItem("11.6"); + TicDupList->UpdateItem("Hz", 2, 1); + TicDupList->SetSelectedItem(info.DefaultNetTicDup); + ExtraTicCheckbox->SetChecked(info.DefaultNetExtraTic); + + GameModesLabel = new TextLabel(this); + CoopCheckbox = new CheckboxLabel(this); + DeathmatchCheckbox = new CheckboxLabel(this); + AltDeathmatchCheckbox = new CheckboxLabel(this); + TeamDeathmatchCheckbox = new CheckboxLabel(this); + TeamLabel = new TextLabel(this); + TeamEdit = new LineEdit(this); + + // These are intentionally not radio buttons, they just act similarly for clarity in the UI. + CoopCheckbox->FuncChanged = [this](bool on) { if (on) { DeathmatchCheckbox->SetChecked(false); TeamDeathmatchCheckbox->SetChecked(false); }}; + DeathmatchCheckbox->FuncChanged = [this](bool on) { if (on) { CoopCheckbox->SetChecked(false); TeamDeathmatchCheckbox->SetChecked(false); }}; + TeamDeathmatchCheckbox->FuncChanged = [this](bool on) { if (on) { CoopCheckbox->SetChecked(false); DeathmatchCheckbox->SetChecked(false); }}; + + switch (info.DefaultNetGameMode) + { + case 0: + CoopCheckbox->SetChecked(true); + break; + case 1: + DeathmatchCheckbox->SetChecked(true); + break; + case 2: + TeamDeathmatchCheckbox->SetChecked(true); + break; + } + AltDeathmatchCheckbox->SetChecked(info.DefaultNetAltDM); + + TeamEdit->SetMaxLength(3); + TeamEdit->SetNumericMode(true); + TeamEdit->SetTextInt(info.DefaultNetHostTeam); + + MaxPlayersEdit = new LineEdit(this); + PortEdit = new LineEdit(this); + MaxPlayersLabel = new TextLabel(this); + PortLabel = new TextLabel(this); + + MaxPlayersEdit->SetMaxLength(2); + MaxPlayersEdit->SetNumericMode(true); + MaxPlayersEdit->SetTextInt(info.DefaultNetPlayers); + PortEdit->SetMaxLength(5); + PortEdit->SetNumericMode(true); + if (info.DefaultNetHostPort > 0) + PortEdit->SetTextInt(info.DefaultNetHostPort); + + MaxPlayerHintLabel = new TextLabel(this); + PortHintLabel = new TextLabel(this); + TeamHintLabel = new TextLabel(this); + + MaxPlayerHintLabel->SetStyleColor("color", Colorf::fromRgba8(160, 160, 160)); + PortHintLabel->SetStyleColor("color", Colorf::fromRgba8(160, 160, 160)); + TeamHintLabel->SetStyleColor("color", Colorf::fromRgba8(160, 160, 160)); +} + +void HostSubPage::SetValues(FStartupSelectionInfo& info) const +{ + info.AdditionalNetArgs = ""; + if (PacketServerCheckbox->GetChecked()) + { + info.AdditionalNetArgs.AppendFormat(" -netmode 1"); + info.DefaultNetMode = 1; + } + else if (PeerToPeerCheckbox->GetChecked()) + { + info.AdditionalNetArgs.AppendFormat(" -netmode 0"); + info.DefaultNetMode = 0; + } + else + { + info.DefaultNetMode = -1; + } + + info.DefaultNetExtraTic = ExtraTicCheckbox->GetChecked(); + if (info.DefaultNetExtraTic) + info.AdditionalNetArgs.AppendFormat(" -extratic"); + + const int dup = TicDupList->GetSelectedItem(); + if (dup > 0) + info.AdditionalNetArgs.AppendFormat(" -dup %d", dup + 1); + info.DefaultNetTicDup = dup; + + info.DefaultNetPlayers = clamp(MaxPlayersEdit->GetTextInt(), 1, MAXPLAYERS); + info.AdditionalNetArgs.AppendFormat(" -host %d", info.DefaultNetPlayers); + const int port = clamp(PortEdit->GetTextInt(), 0, UINT16_MAX); + if (port > 0) + { + info.AdditionalNetArgs.AppendFormat(" -port %d", port); + info.DefaultNetHostPort = port; + } + else + { + info.DefaultNetHostPort = 0; + } + + info.DefaultNetAltDM = AltDeathmatchCheckbox->GetChecked(); + if (CoopCheckbox->GetChecked()) + { + info.AdditionalNetArgs.AppendFormat(" -coop"); + info.DefaultNetGameMode = 0; + } + else if (DeathmatchCheckbox->GetChecked()) + { + if (AltDeathmatchCheckbox->GetChecked()) + info.AdditionalNetArgs.AppendFormat(" -altdeath"); + else + info.AdditionalNetArgs.AppendFormat(" -deathmatch"); + + info.DefaultNetGameMode = 1; + } + else if (TeamDeathmatchCheckbox->GetChecked()) + { + if (AltDeathmatchCheckbox->GetChecked()) + info.AdditionalNetArgs.AppendFormat(" -altdeath"); + else + info.AdditionalNetArgs.AppendFormat(" -deathmatch"); + info.AdditionalNetArgs.AppendFormat(" +teamplay 1"); + info.DefaultNetGameMode = 2; + + int team = 255; + if (!TeamEdit->GetText().empty()) + { + team = TeamEdit->GetTextInt(); + if (team < 0 || team > 255) + team = 255; + } + info.AdditionalNetArgs.AppendFormat(" +team %d", team); + info.DefaultNetHostTeam = team; + } + else + { + info.DefaultNetGameMode = -1; + } +} + +void HostSubPage::UpdateLanguage() +{ + NetModesLabel->SetText(GStrings.GetString("PICKER_NETMODE")); + AutoNetmodeCheckbox->SetText(GStrings.GetString("PICKER_NETAUTO")); + PacketServerCheckbox->SetText(GStrings.GetString("PICKER_NETSERVER")); + PeerToPeerCheckbox->SetText(GStrings.GetString("PICKER_NETPEER")); + + TicDupLabel->SetText(GStrings.GetString("PICKER_NETRATE")); + ExtraTicCheckbox->SetText(GStrings.GetString("PICKER_NETBACKUP")); + + GameModesLabel->SetText(GStrings.GetString("PICKER_GAMEMODE")); + CoopCheckbox->SetText(GStrings.GetString("PICKER_COOP")); + DeathmatchCheckbox->SetText(GStrings.GetString("PICKER_DM")); + AltDeathmatchCheckbox->SetText(GStrings.GetString("PICKER_ALTDM")); + TeamDeathmatchCheckbox->SetText(GStrings.GetString("PICKER_TDM")); + TeamLabel->SetText(GStrings.GetString("PICKER_TEAM")); + + MaxPlayersLabel->SetText(GStrings.GetString("PICKER_PLAYERS")); + PortLabel->SetText(GStrings.GetString("PICKER_PORT")); + + MaxPlayerHintLabel->SetText(GStrings.GetString("PICKER_PLAYERHINT")); + PortHintLabel->SetText(GStrings.GetString("PICKER_PORTHINT")); + TeamHintLabel->SetText(GStrings.GetString("PICKER_TEAMHINT")); +} + +void HostSubPage::OnGeometryChanged() +{ + const double w = GetWidth(); + const double h = GetHeight(); + + constexpr double LabelOfsSize = 90.0; + constexpr double hintOfs = 170.0; + + double y = 0.0; + + MaxPlayersLabel->SetFrameGeometry(0.0, y, LabelOfsSize, MaxPlayersLabel->GetPreferredHeight()); + MaxPlayersEdit->SetFrameGeometry(MaxPlayersLabel->GetWidth(), y, 30.0, EditHeight); + y += EditHeight + 2.0; + + PortLabel->SetFrameGeometry(0.0, y, LabelOfsSize, PortLabel->GetPreferredHeight()); + PortEdit->SetFrameGeometry(PortLabel->GetWidth(), y, 60.0, EditHeight); + + MaxPlayerHintLabel->SetFrameGeometry(hintOfs, 0.0, w - hintOfs, MaxPlayerHintLabel->GetPreferredHeight()); + PortHintLabel->SetFrameGeometry(hintOfs, y, w - hintOfs, PortHintLabel->GetPreferredHeight()); + + y += EditHeight + 7.0; + + const double optionsTop = y; + TicDupLabel->SetFrameGeometry(0.0, y, 100.0, TicDupLabel->GetPreferredHeight()); + y += TicDupLabel->GetPreferredHeight(); + TicDupList->SetFrameGeometry(0.0, y, 100.0, (TicDupList->GetItemAmount() + 1) * 20.0); + y += TicDupList->GetHeight() + ExtraTicCheckbox->GetPreferredHeight() + 2.0; + + ExtraTicCheckbox->SetFrameGeometry(0.0, y, w, ExtraTicCheckbox->GetPreferredHeight()); + y += ExtraTicCheckbox->GetPreferredHeight(); + + const double optionsBottom = y; + y = optionsTop; + + constexpr double NetModeXOfs = 115.0; + NetModesLabel->SetFrameGeometry(NetModeXOfs, y, w - NetModeXOfs, NetModesLabel->GetPreferredHeight()); + y += NetModesLabel->GetPreferredHeight(); + + AutoNetmodeCheckbox->SetFrameGeometry(NetModeXOfs, y, w - NetModeXOfs, AutoNetmodeCheckbox->GetPreferredHeight()); + y += AutoNetmodeCheckbox->GetPreferredHeight(); + + PacketServerCheckbox->SetFrameGeometry(NetModeXOfs, y, w - NetModeXOfs, PacketServerCheckbox->GetPreferredHeight()); + y += PacketServerCheckbox->GetPreferredHeight(); + + PeerToPeerCheckbox->SetFrameGeometry(NetModeXOfs, y, w - NetModeXOfs, PeerToPeerCheckbox->GetPreferredHeight()); + y += PeerToPeerCheckbox->GetPreferredHeight(); + + y = max(optionsBottom, y) + 10.0; + GameModesLabel->SetFrameGeometry(0.0, y, w, GameModesLabel->GetPreferredHeight()); + y += GameModesLabel->GetPreferredHeight(); + + CoopCheckbox->SetFrameGeometry(0.0, y, w, CoopCheckbox->GetPreferredHeight()); + y += CoopCheckbox->GetPreferredHeight(); + + DeathmatchCheckbox->SetFrameGeometry(0.0, y, w, DeathmatchCheckbox->GetPreferredHeight()); + y += DeathmatchCheckbox->GetPreferredHeight(); + + TeamDeathmatchCheckbox->SetFrameGeometry(0.0, y, w, TeamDeathmatchCheckbox->GetPreferredHeight()); + y += TeamDeathmatchCheckbox->GetPreferredHeight() + 2.0; + + TeamLabel->SetFrameGeometry(14.0, y, LabelOfsSize - 14.0, TeamLabel->GetPreferredHeight()); + TeamEdit->SetFrameGeometry(TeamLabel->GetWidth() + 14.0, y, 45.0, EditHeight); + TeamHintLabel->SetFrameGeometry(hintOfs, y, w - hintOfs, TeamHintLabel->GetPreferredHeight()); + y += EditHeight + 2.0; + + AltDeathmatchCheckbox->SetFrameGeometry(0.0, y, w, AltDeathmatchCheckbox->GetPreferredHeight()); + + MainTab->UpdatePlayButton(); +} + +JoinSubPage::JoinSubPage(NetworkPage* main, const FStartupSelectionInfo& info) : Widget(nullptr), MainTab(main) +{ + AddressEdit = new LineEdit(this); + AddressPortEdit = new LineEdit(this); + AddressLabel = new TextLabel(this); + AddressPortLabel = new TextLabel(this); + + AddressEdit->SetText(info.DefaultNetAddress.GetChars()); + AddressPortEdit->SetMaxLength(5); + AddressPortEdit->SetNumericMode(true); + if (info.DefaultNetJoinPort > 0) + AddressPortEdit->SetTextInt(info.DefaultNetJoinPort); + + TeamDeathmatchLabel = new TextLabel(this); + TeamLabel = new TextLabel(this); + TeamEdit = new LineEdit(this); + + TeamEdit->SetMaxLength(3); + TeamEdit->SetNumericMode(true); + TeamEdit->SetTextInt(info.DefaultNetJoinTeam); + + AddressPortHintLabel = new TextLabel(this); + TeamHintLabel = new TextLabel(this); + + AddressPortHintLabel->SetStyleColor("color", Colorf::fromRgba8(160, 160, 160)); + TeamHintLabel->SetStyleColor("color", Colorf::fromRgba8(160, 160, 160)); +} + +void JoinSubPage::SetValues(FStartupSelectionInfo& info) const +{ + FString addr = AddressEdit->GetText(); + info.DefaultNetAddress = addr; + const int port = clamp(AddressPortEdit->GetTextInt(), 0, UINT16_MAX); + if (port > 0) + { + addr.AppendFormat(":%d", port); + info.DefaultNetJoinPort = port; + } + else + { + info.DefaultNetJoinPort = 0; + } + + info.AdditionalNetArgs = ""; + info.AdditionalNetArgs.AppendFormat(" -join %s", addr.GetChars()); + + int team = 255; + if (!TeamEdit->GetText().empty()) + { + team = TeamEdit->GetTextInt(); + if (team < 0 || team > 255) + team = 255; + } + info.AdditionalNetArgs.AppendFormat(" +team %d", team); + info.DefaultNetJoinTeam = team; +} + +void JoinSubPage::UpdateLanguage() +{ + AddressLabel->SetText(GStrings.GetString("PICKER_IP")); + AddressPortLabel->SetText(GStrings.GetString("PICKER_PORT")); + + TeamDeathmatchLabel->SetText(GStrings.GetString("PICKER_TDMLABEL")); + TeamLabel->SetText(GStrings.GetString("PICKER_TEAM")); + + AddressPortHintLabel->SetText(GStrings.GetString("PICKER_PORTHINT")); + TeamHintLabel->SetText(GStrings.GetString("PICKER_TEAMHINT")); +} + +void JoinSubPage::OnGeometryChanged() +{ + const double w = GetWidth(); + const double h = GetHeight(); + + constexpr double LabelOfsSize = 90.0; + constexpr double hintOfs = 170.0; + + double y = 0.0; + + AddressLabel->SetFrameGeometry(0.0, y, LabelOfsSize, AddressLabel->GetPreferredHeight()); + AddressEdit->SetFrameGeometry(AddressLabel->GetWidth(), y, 120.0, EditHeight); + y += EditHeight + 2.0; + + AddressPortLabel->SetFrameGeometry(0.0, y, LabelOfsSize, AddressPortLabel->GetPreferredHeight()); + AddressPortEdit->SetFrameGeometry(AddressPortLabel->GetWidth(), y, 60.0, EditHeight); + + AddressPortHintLabel->SetFrameGeometry(hintOfs, y, w - hintOfs, AddressPortHintLabel->GetPreferredHeight()); + y += EditHeight + 12.0; + + TeamDeathmatchLabel->SetFrameGeometry(0.0, y, w, TeamDeathmatchLabel->GetPreferredHeight()); + y += TeamDeathmatchLabel->GetPreferredHeight(); + + TeamLabel->SetFrameGeometry(0.0, y, LabelOfsSize, TeamLabel->GetPreferredHeight()); + TeamEdit->SetFrameGeometry(TeamLabel->GetWidth(), y, 45.0, EditHeight); + TeamHintLabel->SetFrameGeometry(hintOfs, y, w - hintOfs, TeamHintLabel->GetPreferredHeight()); + + MainTab->UpdatePlayButton(); +} diff --git a/src/launcher/networkpage.h b/src/launcher/networkpage.h new file mode 100644 index 000000000..23cce71cd --- /dev/null +++ b/src/launcher/networkpage.h @@ -0,0 +1,112 @@ +#pragma once + +#include +#include "gstrings.h" + +class LauncherWindow; +class CheckboxLabel; +class LineEdit; +class ListView; +class TextLabel; +class PushButton; +class TabWidget; +struct WadStuff; +struct FStartupSelectionInfo; + +class HostSubPage; +class JoinSubPage; + +class NetworkPage : public Widget +{ +public: + NetworkPage(LauncherWindow* launcher, const FStartupSelectionInfo& info); + void UpdateLanguage(); + void UpdatePlayButton(); + bool IsInHost() const; + void SetValues(FStartupSelectionInfo& info) const; + void InitializeTabs(const FStartupSelectionInfo& info); + +private: + void OnGeometryChanged() override; + void OnSetFocus() override; + void OnIWADsListActivated(); + + LauncherWindow* Launcher = nullptr; + + // Direct hook into the play page for these so their editing is synchronized. + LineEdit* ParametersEdit = nullptr; + ListView* IWADsList = nullptr; + TextLabel* ParametersLabel = nullptr; + + HostSubPage* HostPage = nullptr; + JoinSubPage* JoinPage = nullptr; + TabWidget* StartPages = nullptr; + + LineEdit* SaveFileEdit = nullptr; + TextLabel* SaveFileLabel = nullptr; + CheckboxLabel* SaveFileCheckbox = nullptr; + CheckboxLabel* SaveArgsCheckbox = nullptr; +}; + +class HostSubPage : public Widget +{ +public: + HostSubPage(NetworkPage* main, const FStartupSelectionInfo& info); + void UpdateLanguage(); + void SetValues(FStartupSelectionInfo& info) const; + +private: + void OnGeometryChanged() override; + + NetworkPage* MainTab = nullptr; + + TextLabel* NetModesLabel = nullptr; + CheckboxLabel* AutoNetmodeCheckbox = nullptr; + CheckboxLabel* PeerToPeerCheckbox = nullptr; + CheckboxLabel* PacketServerCheckbox = nullptr; + ListView* TicDupList = nullptr; + TextLabel* TicDupLabel = nullptr; + CheckboxLabel* ExtraTicCheckbox = nullptr; + + TextLabel* GameModesLabel = nullptr; + CheckboxLabel* CoopCheckbox = nullptr; + CheckboxLabel* DeathmatchCheckbox = nullptr; + CheckboxLabel* AltDeathmatchCheckbox = nullptr; + CheckboxLabel* TeamDeathmatchCheckbox = nullptr; + TextLabel* TeamLabel = nullptr; + LineEdit* TeamEdit = nullptr; + + LineEdit* MaxPlayersEdit = nullptr; + LineEdit* PortEdit = nullptr; + TextLabel* MaxPlayersLabel = nullptr; + TextLabel* PortLabel = nullptr; + + TextLabel* MaxPlayerHintLabel = nullptr; + TextLabel* PortHintLabel = nullptr; + TextLabel* TeamHintLabel = nullptr; +}; + +class JoinSubPage : public Widget +{ +public: + JoinSubPage(NetworkPage* main, const FStartupSelectionInfo& info); + void UpdateLanguage(); + void SetValues(FStartupSelectionInfo& info) const; + +private: + void OnGeometryChanged() override; + + NetworkPage* MainTab = nullptr; + + LineEdit* AddressEdit = nullptr; + LineEdit* AddressPortEdit = nullptr; + TextLabel* AddressLabel = nullptr; + TextLabel* AddressPortLabel = nullptr; + + TextLabel* TeamDeathmatchLabel = nullptr; + TextLabel* TeamLabel = nullptr; + LineEdit* TeamEdit = nullptr; + + TextLabel* AddressPortHintLabel = nullptr; + TextLabel* TeamHintLabel = nullptr; +}; diff --git a/src/launcher/playgamepage.cpp b/src/launcher/playgamepage.cpp index 53676b94e..bdfb2309d 100644 --- a/src/launcher/playgamepage.cpp +++ b/src/launcher/playgamepage.cpp @@ -7,8 +7,9 @@ #include #include #include +#include -PlayGamePage::PlayGamePage(LauncherWindow* launcher, WadStuff* wads, int numwads, int defaultiwad) : Widget(nullptr), Launcher(launcher) +PlayGamePage::PlayGamePage(LauncherWindow* launcher, const FStartupSelectionInfo& info) : Widget(nullptr), Launcher(launcher) { WelcomeLabel = new TextLabel(this); VersionLabel = new TextLabel(this); @@ -17,44 +18,43 @@ PlayGamePage::PlayGamePage(LauncherWindow* launcher, WadStuff* wads, int numwads ParametersLabel = new TextLabel(this); GamesList = new ListView(this); ParametersEdit = new LineEdit(this); + SaveArgsCheckbox = new CheckboxLabel(this); - for (int i = 0; i < numwads; i++) + SaveArgsCheckbox->SetChecked(info.bSaveArgs); + if (!info.DefaultArgs.IsEmpty()) + ParametersEdit->SetText(info.DefaultArgs.GetChars()); + + for (const auto& wad : *info.Wads) { - const char* filepart = strrchr(wads[i].Path.GetChars(), '/'); - if (filepart == NULL) - filepart = wads[i].Path.GetChars(); + const char* filepart = strrchr(wad.Path.GetChars(), '/'); + if (filepart == nullptr) + filepart = wad.Path.GetChars(); else - filepart++; + ++filepart; FString work; - if (*filepart) work.Format("%s (%s)", wads[i].Name.GetChars(), filepart); - else work = wads[i].Name.GetChars(); + if (*filepart) + work.Format("%s (%s)", wad.Name.GetChars(), filepart); + else + work = wad.Name.GetChars(); GamesList->AddItem(work.GetChars()); } - if (defaultiwad >= 0 && defaultiwad < numwads) + if (info.DefaultIWAD >= 0 && info.DefaultIWAD < info.Wads->Size()) { - GamesList->SetSelectedItem(defaultiwad); - GamesList->ScrollToItem(defaultiwad); + GamesList->SetSelectedItem(info.DefaultIWAD); + GamesList->ScrollToItem(info.DefaultIWAD); } GamesList->OnActivated = [=]() { OnGamesListActivated(); }; } -void PlayGamePage::SetExtraArgs(const std::string& args) +void PlayGamePage::SetValues(FStartupSelectionInfo& info) const { - ParametersEdit->SetText(args); -} - -std::string PlayGamePage::GetExtraArgs() -{ - return ParametersEdit->GetText(); -} - -int PlayGamePage::GetSelectedGame() -{ - return GamesList->GetSelectedItem(); + info.DefaultIWAD = GamesList->GetSelectedItem(); + info.DefaultArgs = ParametersEdit->GetText(); + info.bSaveArgs = SaveArgsCheckbox->GetChecked(); } void PlayGamePage::UpdateLanguage() @@ -67,6 +67,7 @@ void PlayGamePage::UpdateLanguage() FString versionText = GStrings.GetString("PICKER_VERSION"); versionText.Substitute("%s", GetVersionString()); VersionLabel->SetText(versionText.GetChars()); + SaveArgsCheckbox->SetText(GStrings.GetString("PICKER_REMPARM")); } void PlayGamePage::OnGamesListActivated() @@ -95,12 +96,13 @@ void PlayGamePage::OnGeometryChanged() double listViewTop = y; - y = GetHeight() - 10.0; + y = GetHeight() - 10.0 - SaveArgsCheckbox->GetPreferredHeight(); + SaveArgsCheckbox->SetFrameGeometry(0.0, y, GetWidth(), SaveArgsCheckbox->GetPreferredHeight()); + y -= 2.0; double editHeight = 24.0; y -= editHeight; ParametersEdit->SetFrameGeometry(0.0, y, GetWidth(), editHeight); - y -= 5.0; double labelHeight = ParametersLabel->GetPreferredHeight(); y -= labelHeight; @@ -109,4 +111,6 @@ void PlayGamePage::OnGeometryChanged() double listViewBottom = y - 10.0; GamesList->SetFrameGeometry(0.0, listViewTop, GetWidth(), std::max(listViewBottom - listViewTop, 0.0)); + + Launcher->UpdatePlayButton(); } diff --git a/src/launcher/playgamepage.h b/src/launcher/playgamepage.h index dd3bd170a..a41b8f5d4 100644 --- a/src/launcher/playgamepage.h +++ b/src/launcher/playgamepage.h @@ -6,18 +6,16 @@ class LauncherWindow; class TextLabel; class ListView; class LineEdit; +class CheckboxLabel; struct WadStuff; +struct FStartupSelectionInfo; class PlayGamePage : public Widget { public: - PlayGamePage(LauncherWindow* launcher, WadStuff* wads, int numwads, int defaultiwad); + PlayGamePage(LauncherWindow* launcher, const FStartupSelectionInfo& info); void UpdateLanguage(); - - void SetExtraArgs(const std::string& args); - std::string GetExtraArgs(); - - int GetSelectedGame(); + void SetValues(FStartupSelectionInfo& info) const; private: void OnGeometryChanged() override; @@ -32,4 +30,5 @@ private: TextLabel* ParametersLabel = nullptr; ListView* GamesList = nullptr; LineEdit* ParametersEdit = nullptr; + CheckboxLabel* SaveArgsCheckbox = nullptr; }; diff --git a/src/launcher/settingspage.cpp b/src/launcher/settingspage.cpp index 01b6817df..14b9f2db0 100644 --- a/src/launcher/settingspage.cpp +++ b/src/launcher/settingspage.cpp @@ -9,14 +9,7 @@ #include #include -#ifdef RENDER_BACKENDS -EXTERN_CVAR(Int, vid_preferbackend); -#endif - -EXTERN_CVAR(String, language) -EXTERN_CVAR(Bool, queryiwad); - -SettingsPage::SettingsPage(LauncherWindow* launcher, int* autoloadflags) : Widget(nullptr), Launcher(launcher), AutoloadFlags(autoloadflags) +SettingsPage::SettingsPage(LauncherWindow* launcher, const FStartupSelectionInfo& info) : Widget(nullptr), Launcher(launcher) { LangLabel = new TextLabel(this); GeneralLabel = new TextLabel(this); @@ -28,30 +21,27 @@ SettingsPage::SettingsPage(LauncherWindow* launcher, int* autoloadflags) : Widge BrightmapsCheckbox = new CheckboxLabel(this); WidescreenCheckbox = new CheckboxLabel(this); + FullscreenCheckbox->SetChecked(info.DefaultFullscreen); + DontAskAgainCheckbox->SetChecked(!info.DefaultQueryIWAD); + + DisableAutoloadCheckbox->SetChecked(info.DefaultStartFlags & 1); + LightsCheckbox->SetChecked(info.DefaultStartFlags & 2); + BrightmapsCheckbox->SetChecked(info.DefaultStartFlags & 4); + WidescreenCheckbox->SetChecked(info.DefaultStartFlags & 8); + #ifdef RENDER_BACKENDS BackendLabel = new TextLabel(this); VulkanCheckbox = new CheckboxLabel(this); OpenGLCheckbox = new CheckboxLabel(this); GLESCheckbox = new CheckboxLabel(this); -#endif - FullscreenCheckbox->SetChecked(vid_fullscreen); - DontAskAgainCheckbox->SetChecked(!queryiwad); - - int flags = *autoloadflags; - DisableAutoloadCheckbox->SetChecked(flags & 1); - LightsCheckbox->SetChecked(flags & 2); - BrightmapsCheckbox->SetChecked(flags & 4); - WidescreenCheckbox->SetChecked(flags & 8); - -#ifdef RENDER_BACKENDS OpenGLCheckbox->SetRadioStyle(true); VulkanCheckbox->SetRadioStyle(true); GLESCheckbox->SetRadioStyle(true); OpenGLCheckbox->FuncChanged = [this](bool on) { if (on) { VulkanCheckbox->SetChecked(false); GLESCheckbox->SetChecked(false); }}; VulkanCheckbox->FuncChanged = [this](bool on) { if (on) { OpenGLCheckbox->SetChecked(false); GLESCheckbox->SetChecked(false); }}; GLESCheckbox->FuncChanged = [this](bool on) { if (on) { VulkanCheckbox->SetChecked(false); OpenGLCheckbox->SetChecked(false); }}; - switch (vid_preferbackend) + switch (info.DefaultBackend) { case 0: OpenGLCheckbox->SetChecked(true); @@ -101,32 +91,33 @@ SettingsPage::SettingsPage(LauncherWindow* launcher, int* autoloadflags) : Widge for (auto& l : languages) { LangList->AddItem(l.second.GetChars()); - if (!l.first.CompareNoCase(::language)) + if (!l.first.CompareNoCase(info.DefaultLanguage)) LangList->SetSelectedItem(i); - i++; + ++i; } LangList->OnChanged = [=](int i) { OnLanguageChanged(i); }; } -void SettingsPage::Save() +void SettingsPage::SetValues(FStartupSelectionInfo& info) const { - vid_fullscreen = FullscreenCheckbox->GetChecked(); - queryiwad = !DontAskAgainCheckbox->GetChecked(); + info.DefaultFullscreen = FullscreenCheckbox->GetChecked(); + info.DefaultQueryIWAD = !DontAskAgainCheckbox->GetChecked(); + info.DefaultLanguage = languages[LangList->GetSelectedItem()].first.GetChars(); int flags = 0; if (DisableAutoloadCheckbox->GetChecked()) flags |= 1; if (LightsCheckbox->GetChecked()) flags |= 2; if (BrightmapsCheckbox->GetChecked()) flags |= 4; if (WidescreenCheckbox->GetChecked()) flags |= 8; - *AutoloadFlags = flags; + info.DefaultStartFlags = flags; #ifdef RENDER_BACKENDS int v = 1; if (OpenGLCheckbox->GetChecked()) v = 0; else if (VulkanCheckbox->GetChecked()) v = 1; else if (GLESCheckbox->GetChecked()) v = 2; - if (v != vid_preferbackend) vid_preferbackend = v; + info.DefaultBackend = v; #endif } @@ -152,8 +143,7 @@ void SettingsPage::UpdateLanguage() void SettingsPage::OnLanguageChanged(int i) { - ::language = languages[i].first.GetChars(); - GStrings.UpdateLanguage(::language); // CVAR callbacks are not active yet. + GStrings.UpdateLanguage(languages[i].first.GetChars()); UpdateLanguage(); Update(); Launcher->UpdateLanguage(); @@ -204,4 +194,6 @@ void SettingsPage::OnGeometryChanged() y += LangLabel->GetPreferredHeight(); LangList->SetFrameGeometry(0.0, y, w, std::max(h - y, 0.0)); } + + Launcher->UpdatePlayButton(); } diff --git a/src/launcher/settingspage.h b/src/launcher/settingspage.h index 77a6ff048..1b96a1e6c 100644 --- a/src/launcher/settingspage.h +++ b/src/launcher/settingspage.h @@ -3,20 +3,20 @@ #include #include "gstrings.h" -// #define RENDER_BACKENDS +#define RENDER_BACKENDS class LauncherWindow; class TextLabel; class CheckboxLabel; class ListView; +struct FStartupSelectionInfo; class SettingsPage : public Widget { public: - SettingsPage(LauncherWindow* launcher, int* autoloadflags); + SettingsPage(LauncherWindow* launcher, const FStartupSelectionInfo& info); void UpdateLanguage(); - - void Save(); + void SetValues(FStartupSelectionInfo& info) const; private: void OnLanguageChanged(int i); @@ -41,8 +41,6 @@ private: #endif ListView* LangList = nullptr; - int* AutoloadFlags = nullptr; - TArray> languages; bool hideLanguage = false; };