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