diff --git a/source/build/src/sdlayer.cpp b/source/build/src/sdlayer.cpp index d5e22a5f8..65f3bb8ab 100644 --- a/source/build/src/sdlayer.cpp +++ b/source/build/src/sdlayer.cpp @@ -1780,7 +1780,7 @@ int32_t handleevents_sdlcommon(SDL_Event *ev) break; case SDL_QUIT: - quitevent = 1; + throw ExitEvent(0); // completely bypass the hackery in the games to block Alt-F4. return -1; } diff --git a/source/common/menu/listmenu.cpp b/source/common/menu/listmenu.cpp index df1cfa725..8d1e3f164 100644 --- a/source/common/menu/listmenu.cpp +++ b/source/common/menu/listmenu.cpp @@ -178,7 +178,7 @@ bool DListMenu::MenuEvent (int mkey, bool fromcontroller) return true; case MKEY_Enter: - if (mDesc->mSelectedItem >= 0 && mDesc->mItems[mDesc->mSelectedItem]->Activate()) + if (mDesc->mSelectedItem >= 0 && mDesc->mItems[mDesc->mSelectedItem]->Activate(mDesc->mMenuName)) { gi->MenuChooseSound(); } @@ -320,7 +320,7 @@ void FListMenuItem::DrawSelector(int xofs, int yofs, FTexture *tex) } } -bool FListMenuItem::Activate() +bool FListMenuItem::Activate(FName) { return false; // cannot be activated } @@ -476,10 +476,9 @@ bool FListMenuItemSelectable::Selectable() return mEnabled && !mHidden; } -bool FListMenuItemSelectable::Activate() +bool FListMenuItemSelectable::Activate(FName caller) { - M_SetMenu(mAction, mParam); - return true; + return M_SetMenu(mAction, mParam, caller); } FName FListMenuItemSelectable::GetAction(int *pparam) diff --git a/source/common/menu/menu.cpp b/source/common/menu/menu.cpp index 75c8b3382..170132509 100644 --- a/source/common/menu/menu.cpp +++ b/source/common/menu/menu.cpp @@ -153,7 +153,7 @@ bool DMenu::MenuEvent (int mkey, bool fromcontroller) { case MKEY_Back: { - if (scriptID != 1) + if (scriptID != 0) { Close(); //S_Sound (CHAN_VOICE | CHAN_UI, DMenu::CurrentMenu != NULL? "menu/backup" : "menu/clear", snd_menuvolume, ATTN_NONE); @@ -301,10 +301,17 @@ bool DMenu::TranslateKeyboardEvents() void M_StartControlPanel (bool makeSound) { + static bool created = false; // intro might call this repeatedly if (DMenu::CurrentMenu != NULL) return; + if (!created) // Cannot do this earlier. + { + created = true; + M_CreateMenus(); + } + buttonMap.ResetButtonStates (); for (int i = 0; i < NUM_MKEYS; ++i) { @@ -350,7 +357,7 @@ void M_ActivateMenu(DMenu *menu) // //============================================================================= -void M_SetMenu(FName menu, int param) +bool M_SetMenu(FName menu, int param, FName caller) { if (DrawBackground == -1) { @@ -358,32 +365,40 @@ void M_SetMenu(FName menu, int param) else DrawBackground = 0; } // some menus need some special treatment (needs to be adjusted for the various frontends. -#if 0 + switch (caller) + { + case NAME_EpisodeMenu: + // sent from the episode menu + GameStartupInfo.Episode = param; + GameStartupInfo.CustomLevel1 = GameStartupInfo.CustomLevel2 = -1; + GameStartupInfo.Skill = gDefaultSkill; + break; + + case NAME_CustomGameMenu: + GameStartupInfo.CustomLevel1 = param; + GameStartupInfo.Episode = GameStartupInfo.CustomLevel2 = -1; + GameStartupInfo.Skill = gDefaultSkill; + // gi->CustomMenuSelection(-1, param); + break; + + case NAME_CustomSubMenu1: + GameStartupInfo.CustomLevel2 = param; + // gi->CustomMenuSelection(GameStartupInfo.CustomLevel1, param); + menu = FName(ENamedName(menu + param)); + break; + + case NAME_SkillMenu: + GameStartupInfo.Skill = param; + break; + } + switch (menu) { - case NAME_Episodemenu: - // sent from the player class menu - GameStartupInfo.Skill = -1; - GameStartupInfo.Episode = -1; - GameStartupInfo.PlayerClass = - param == -1000? NULL : - param == -1? "Random" : GetPrintableDisplayName(PlayerClasses[param].Type); - break; - - case NAME_Skillmenu: - // sent from the episode menu - - if ((gameinfo.flags & GI_SHAREWARE) && param > 0) - { - // Only Doom and Heretic have multi-episode shareware versions. - M_StartMessage(GStrings("SWSTRING"), 1); - return; - } - - GameStartupInfo.Episode = param; - M_StartupSkillMenu(&GameStartupInfo); // needs player class name from class menu (later) - break; + case NAME_StartGame: + // gi->StartGame(&GameStartupInfo); + return false; +#if 0 case NAME_StartgameConfirm: { // sent from the skill menu for a skill that needs to be confirmed @@ -395,21 +410,6 @@ void M_SetMenu(FName menu, int param) return; } - case NAME_Startgame: - // sent either from skill menu or confirmation screen. Skill gets only set if sent from skill menu - // Now we can finally start the game. Ugh... - GameStartupInfo.Skill = param; - case NAME_StartgameConfirmed: - - G_DeferedInitNew (&GameStartupInfo); - if (gamestate == GS_FULLCONSOLE) - { - gamestate = GS_HIDECONSOLE; - gameaction = ga_newgame; - } - M_ClearMenus (); - return; - case NAME_Savegamemenu: if (!usergame || (players[consoleplayer].health <= 0 && !multiplayer) || gamestate != GS_LEVEL) { @@ -417,8 +417,8 @@ void M_SetMenu(FName menu, int param) M_StartMessage (GStrings("SAVEDEAD"), 1); return; } - } #endif + } // End of special checks @@ -439,7 +439,7 @@ void M_SetMenu(FName menu, int param) if (ld->mAutoselect >= 0 && ld->mAutoselect < (int)ld->mItems.Size()) { // recursively activate the autoselected item without ever creating this menu. - ld->mItems[ld->mAutoselect]->Activate(); + ld->mItems[ld->mAutoselect]->Activate(ld->mMenuName); } else { @@ -462,6 +462,7 @@ void M_SetMenu(FName menu, int param) } newmenu->Init(DMenu::CurrentMenu, ld); M_ActivateMenu(newmenu); + return true; } } else if ((*desc)->mType == MDESC_OptionsMenu) @@ -473,7 +474,7 @@ void M_SetMenu(FName menu, int param) newmenu->Init(DMenu::CurrentMenu, ld); M_ActivateMenu(newmenu); } - return; + return true; } else { @@ -486,12 +487,13 @@ void M_SetMenu(FName menu, int param) DMenu *newmenu = (DMenu*)menuclass->CreateNew(); newmenu->mParentMenu = DMenu::CurrentMenu; M_ActivateMenu(newmenu); - return; + return true; } } */ } Printf("Attempting to open menu of unknown type '%s'\n", menu.GetChars()); + return false; } //============================================================================= @@ -790,7 +792,6 @@ void M_Init (void) RegisterDukeMenus(); timerSetCallback(M_Ticker); M_ParseMenuDefs(); - M_CreateMenus(); } diff --git a/source/common/menu/menu.h b/source/common/menu/menu.h index 244e1ba1b..13f549c6d 100644 --- a/source/common/menu/menu.h +++ b/source/common/menu/menu.h @@ -17,6 +17,7 @@ enum EMax { MAXSKILLS = 7, MAXVOLUMES = 7, + MAXMENUGAMEPLAYENTRIES = 7, }; // These get filled in by the map definition parsers of the front ends. @@ -24,11 +25,40 @@ extern FString gSkillNames[MAXSKILLS]; extern FString gVolumeNames[MAXVOLUMES]; extern FString gVolumeSubtitles[MAXVOLUMES]; extern int32_t gVolumeFlags[MAXVOLUMES]; +extern int gDefaultVolume, gDefaultSkill; const int MENU_TICRATE = 30; extern bool help_disabled, credits_disabled; extern int g_currentMenu; +enum +{ + EF_HIDEFROMSP = 1 << 0, +}; + +enum MenuGameplayEntryFlags +{ + MGE_Locked = 1u << 0u, + MGE_Hidden = 1u << 1u, + MGE_UserContent = 1u << 2u, +}; + +typedef struct MenuGameplayEntry +{ + char name[64]; + uint8_t flags; + + bool isValid() const { return name[0] != '\0'; } +} MenuGameplayEntry; + +typedef struct MenuGameplayStemEntry +{ + MenuGameplayEntry entry; + MenuGameplayEntry subentries[MAXMENUGAMEPLAYENTRIES]; +} MenuGameplayStemEntry; + +extern MenuGameplayStemEntry g_MenuGameplayEntries[MAXMENUGAMEPLAYENTRIES]; + enum EMenuState : int { @@ -86,9 +116,10 @@ enum ENativeFontValues struct FGameStartup { - const char *PlayerClass; int Episode; int Skill; + int CustomLevel1; + int CustomLevel2; }; extern FGameStartup GameStartupInfo; @@ -345,7 +376,7 @@ public: virtual void Ticker(); virtual void Drawer(DListMenu *menu, const vec2_t& origin, bool selected); virtual bool Selectable(); - virtual bool Activate(); + virtual bool Activate(FName caller); virtual FName GetAction(int *pparam); virtual bool SetString(int i, const char *s); virtual bool GetString(int i, char *s, int len); @@ -363,6 +394,7 @@ public: void SetX(int x) { mXpos = x; } void SetY(int x) { mYpos = x; } void SetHeight(int x) { mHeight = x; } + void SetAction(FName action) { mAction = action; } }; class FListMenuItemStaticPatch : public FListMenuItem @@ -407,7 +439,7 @@ public: bool CheckCoordinate(int x, int y) override; bool Selectable() override; bool CheckHotkey(int c) override; - bool Activate() override; + bool Activate(FName caller) override; bool MouseEvent(int type, int x, int y) override; FName GetAction(int *pparam) override; }; @@ -636,9 +668,11 @@ void M_ParseMenuDefs(); void M_StartupSkillMenu(FGameStartup *gs); int M_GetDefaultSkill(); void M_StartControlPanel (bool makeSound); -void M_SetMenu(FName menu, int param = -1); +bool M_SetMenu(FName menu, int param = -1, FName callingMenu = NAME_None); void M_NotifyNewSave (const char *file, const char *title, bool okForQuicksave); void M_StartMessage(const char *message, int messagemode, FName action = NAME_None); +void M_UnhideCustomMenu(int menu, int itemmask); + void I_SetMouseCapture(); void I_ReleaseMouseCapture(); diff --git a/source/common/menu/menudef.cpp b/source/common/menu/menudef.cpp index 92316cc5d..49920aef3 100644 --- a/source/common/menu/menudef.cpp +++ b/source/common/menu/menudef.cpp @@ -49,10 +49,13 @@ void ClearSaveGames(); +// Menu-relevant content that gets filled in by scripts. This will get processed after the game has loaded. FString gSkillNames[MAXSKILLS]; FString gVolumeNames[MAXVOLUMES]; FString gVolumeSubtitles[MAXVOLUMES]; int32_t gVolumeFlags[MAXVOLUMES]; +int gDefaultVolume = 0, gDefaultSkill = 1; +MenuGameplayStemEntry g_MenuGameplayEntries[MAXMENUGAMEPLAYENTRIES]; MenuDescriptorList MenuDescriptors; static FListMenuDescriptor DefaultListMenuSettings; // contains common settings for all list menus @@ -982,10 +985,34 @@ void M_ParseMenuDefs() } +//============================================================================= +// +// Unlocks a custom menu from a script +// +//============================================================================= + +void M_UnhideCustomMenu(int menu, int iSet) +{ + FName menuname = FName(ENamedName(NAME_CustomSubMenu1 + menu)); + auto desc = MenuDescriptors.CheckKey(menuname); + if (desc != NULL && (*desc)->mType == MDESC_ListMenu) + { + FListMenuDescriptor* ld = static_cast(*desc); + for (unsigned int b = 0; b < MAXMENUGAMEPLAYENTRIES; ++b) + { + if (b >= ld->mItems.Size()) return; + if (iSet & (1u << b)) + { + ld->mItems[b]->mHidden = false; + ld->mItems[b]->mEnabled = true; + } + } + } +} + //============================================================================= // // Creates the episode menu -// Falls back on an option menu if there's not enough screen space to show all episodes // //============================================================================= @@ -994,68 +1021,130 @@ static void BuildEpisodeMenu() // Build episode menu int addedVolumes = 0; FMenuDescriptor **desc = MenuDescriptors.CheckKey(NAME_EpisodeMenu); - if (desc != NULL) + if (desc != NULL && (*desc)->mType == MDESC_ListMenu) { - if ((*desc)->mType == MDESC_ListMenu) + FListMenuDescriptor *ld = static_cast(*desc); + ld->mSelectedItem = gDefaultVolume; + + for (int i = 0; i < MAXVOLUMES; i++) { - FListMenuDescriptor *ld = static_cast(*desc); - ld->mSelectedItem = 1; + if (gVolumeNames[i].IsNotEmpty() && !(gVolumeFlags[i] & EF_HIDEFROMSP)) - for (int i = 0; i < MAXVOLUMES; i++) { - if (gVolumeNames[i].IsNotEmpty()) + auto it = new FListMenuItemNativeText(ld->mXpos, 0, 0, gVolumeNames[i][0], gVolumeNames[i], NIT_BigFont, NIT_ActiveState, 1, NAME_SkillMenu, i + 1); + ld->mItems.Push(it); + addedVolumes++; + if (gVolumeSubtitles[i].IsNotEmpty()) { - auto it = new FListMenuItemNativeText(ld->mXpos, 0, 0, gVolumeNames[i][0], gVolumeNames[i], NIT_BigFont, NIT_ActiveState, 1, NAME_SkillMenu, i + 1); - ld->mItems.Push(it); - addedVolumes++; - if (gVolumeSubtitles[i].IsNotEmpty()) - { - //auto it = new FListMenuItemNativeStaticText(ld->mXpos, gVolumeSubtitles[i], NIT_SmallFont); - //ld->mItems.Push(it); - } - } - if (1 /*CheckUserMaps()*/) - { - //auto it = new FListMenuItemNativeStaticText(ld->mXpos, "", NIT_SmallFont); // empty entry as spacer. + //auto it = new FListMenuItemNativeStaticText(ld->mXpos, gVolumeSubtitles[i], NIT_SmallFont); //ld->mItems.Push(it); + } + } + } + if (1 /*CheckUserMaps()*/) + { + //auto it = new FListMenuItemNativeStaticText(ld->mXpos, "", NIT_SmallFont); // empty entry as spacer. + //ld->mItems.Push(it); - auto it = new FListMenuItemNativeText(ld->mXpos, 0, 0, 0, "$MNU_USERMAP", NIT_BigFont, NIT_ActiveState, 1, NAME_SkillMenu, i + 1); - ld->mItems.Push(it); - addedVolumes++; - if (g_gameType & GAMEFLAG_SW) // fixme: make this game independent. + auto it = new FListMenuItemNativeText(ld->mXpos, 0, 0, 0, "$MNU_USERMAP", NIT_BigFont, NIT_ActiveState, 1, NAME_UsermapMenu); + ld->mItems.Push(it); + addedVolumes++; + if (g_gameType & GAMEFLAG_SW) // fixme: make this game independent. + { + //auto it = new FListMenuItemNativeStaticText(ld->mXpos, "$MNU_SELECTUSERMAP", NIT_SmallFont); + //ld->mItems.Push(it); + } + } + if (addedVolumes == 1) + { + ld->mAutoselect = 0; + } + } + + // Build skill menu + int addedSkills = 0; + desc = MenuDescriptors.CheckKey(NAME_SkillMenu); + if (desc != NULL && (*desc)->mType == MDESC_ListMenu) + { + FListMenuDescriptor* ld = static_cast(*desc); + ld->mSelectedItem = gDefaultSkill; + + for (int i = 0; i < MAXSKILLS; i++) + { + if (gSkillNames[i].IsNotEmpty()) + { + auto it = new FListMenuItemNativeText(ld->mXpos, 0, 0, gSkillNames[i][0], gSkillNames[i], NIT_BigFont, NIT_ActiveState, 1, NAME_StartGame, i); + ld->mItems.Push(it); + addedSkills++; + } + } + if (addedSkills == 0) + { + // Need to add one item with the default skill so that the menu does not break. + auto it = new FListMenuItemNativeText(ld->mXpos, 0, 0, 0, "", NIT_BigFont, NIT_ActiveState, 1, NAME_StartGame, gDefaultSkill); + ld->mItems.Push(it); + } + if (addedSkills == 1) + { + ld->mAutoselect = 0; + } + } + + if (g_MenuGameplayEntries[0].entry.isValid()) + { + int e = 0; + FMenuDescriptor** desc = MenuDescriptors.CheckKey(NAME_CustomGameMenu); + if (desc != NULL && (*desc)->mType == MDESC_ListMenu) + { + FListMenuDescriptor* ld = static_cast(*desc); + + for (MenuGameplayStemEntry const& stem : g_MenuGameplayEntries) + { + MenuGameplayEntry const& entry = stem.entry; + if (!entry.isValid()) + break; + + int s = 0; + FMenuDescriptor** sdesc = MenuDescriptors.CheckKey(FName(ENamedName(NAME_CustomSubMenu1 + e))); + if (sdesc != NULL && (*sdesc)->mType == MDESC_ListMenu) + { + FListMenuDescriptor* ld = static_cast(*desc); + + for (MenuGameplayEntry const& subentry : stem.subentries) { - //auto it = new FListMenuItemNativeStaticText(ld->mXpos, "$MNU_SELECTUSERMAP", NIT_SmallFont); - //ld->mItems.Push(it); + if (!subentry.isValid()) + break; + + auto li = new FListMenuItemNativeText(ld->mXpos, 0, 0, 0, subentry.name, NIT_BigFont, NIT_ActiveColor, 1.f, subentry.flags & MGE_UserContent ? NAME_UsermapMenu : NAME_SkillMenu); + + if (subentry.flags & MGE_Locked) li->mEnabled = false; + if (subentry.flags & MGE_Hidden) li->mHidden = true; + + ++s; } } - if (addedVolumes == 1) + FName link = entry.flags & MGE_UserContent ? NAME_UsermapMenu : s == 0 ? NAME_SkillMenu : NAME_CustomSubMenu1; + + auto li = new FListMenuItemNativeText(ld->mXpos, 0, 0, 0, entry.name, NIT_BigFont, NIT_ActiveColor, 1.f, link, link == NAME_CustomSubMenu1 ? e : -1); + if (entry.flags & MGE_Locked) li->mEnabled = false; + if (entry.flags & MGE_Hidden) li->mHidden = true; + e++; + } + } + if (e > 0) + { + FMenuDescriptor** desc = MenuDescriptors.CheckKey(NAME_MainMenu); + if (desc != NULL && (*desc)->mType == MDESC_ListMenu) + { + FListMenuDescriptor* ld = static_cast(*desc); + auto li = ld->mItems[0]; + if (li->GetAction(nullptr) == NAME_EpisodeMenu) { - ld->mAutoselect = 0; + li->SetAction(NAME_CustomGameMenu); } } } } -#if 0 - if (gVolumeNames[i].IsNotEmpty()) - { - if (!(gVolumeFlags[i] & EF_HIDEFROMSP)) - { - MEL_EPISODE[i] = &ME_EPISODE[i]; - ME_EPISODE[i] = ME_EPISODE_TEMPLATE; - ME_EPISODE[i].name = gVolumeNames[i]; - } - - // if (!(EpisodeFlags[i] & EF_HIDEFROMMP)) - { - MEOSN_NetEpisodes[k] = gVolumeNames[i]; - MEOSV_NetEpisodes[k] = i; - - k++; - } - } - M_EPISODE.numEntries = g_volumeCnt + 2; - -#endif } //============================================================================= @@ -1141,181 +1230,6 @@ void M_CreateMenus() #endif } -//============================================================================= -// -// The skill menu must be refeshed each time it starts up -// -//============================================================================= -extern int restart; - -void M_StartupSkillMenu(FGameStartup *gs) -{ -#if 0 - static int done = -1; - bool success = false; - FMenuDescriptor **desc = MenuDescriptors.CheckKey(NAME_Skillmenu); - if (desc != NULL) - { - if ((*desc)->mType == MDESC_ListMenu) - { - FListMenuDescriptor *ld = static_cast(*desc); - int x = ld->mXpos; - int y = ld->mYpos; - - // Delete previous contents - for(unsigned i=0; imItems.Size(); i++) - { - FName n = ld->mItems[i]->GetAction(NULL); - if (n == NAME_Startgame || n == NAME_StartgameConfirm) - { - for(unsigned j=i; jmItems.Size(); j++) - { - delete ld->mItems[j]; - } - ld->mItems.Resize(i); - break; - } - } - - if (done != restart) - { - done = restart; - int defskill = DefaultSkill; - if ((unsigned int)defskill >= AllSkills.Size()) - { - defskill = (AllSkills.Size() - 1) / 2; - } - ld->mSelectedItem = ld->mItems.Size() + defskill; - - int posy = y; - int topy = posy; - - // Get lowest y coordinate of any static item in the menu - for(unsigned i = 0; i < ld->mItems.Size(); i++) - { - int y = ld->mItems[i]->GetY(); - if (y < topy) topy = y; - } - - // center the menu on the screen if the top space is larger than the bottom space - int totalheight = posy + AllSkills.Size() * ld->mLinespacing - topy; - - if (totalheight < 190 || AllSkills.Size() == 1) - { - int newtop = (200 - totalheight + topy) / 2; - int topdelta = newtop - topy; - if (topdelta < 0) - { - for(unsigned i = 0; i < ld->mItems.Size(); i++) - { - ld->mItems[i]->OffsetPositionY(topdelta); - } - y = ld->mYpos = posy - topdelta; - } - } - else - { - // too large - delete ld; - desc = NULL; - done = false; - goto fail; - } - } - - unsigned firstitem = ld->mItems.Size(); - for(unsigned int i = 0; i < AllSkills.Size(); i++) - { - FSkillInfo &skill = AllSkills[i]; - FListMenuItem *li; - // Using a different name for skills that must be confirmed makes handling this easier. - FName action = (skill.MustConfirm && !AllEpisodes[gs->Episode].mNoSkill) ? - NAME_StartgameConfirm : NAME_Startgame; - FString *pItemText = NULL; - if (gs->PlayerClass != NULL) - { - pItemText = skill.MenuNamesForPlayerClass.CheckKey(gs->PlayerClass); - } - - if (skill.PicName.Len() != 0 && pItemText == NULL) - { - FTextureID tex = GetMenuTexture(skill.PicName); - li = new FListMenuItemPatch(ld->mXpos, y, ld->mLinespacing, skill.Shortcut, tex, action, i); - } - else - { - EColorRange color = (EColorRange)skill.GetTextColor(); - if (color == CR_UNTRANSLATED) color = ld->mFontColor; - li = new FListMenuItemText(x, y, ld->mLinespacing, skill.Shortcut, - pItemText? *pItemText : skill.MenuName, ld->mFont, color,ld->mFontColor2, action, i); - } - ld->mItems.Push(li); - y += ld->mLinespacing; - } - if (AllEpisodes[gs->Episode].mNoSkill || AllSkills.Size() == 1) - { - ld->mAutoselect = firstitem + M_GetDefaultSkill(); - } - else - { - ld->mAutoselect = -1; - } - success = true; - } - } - if (success) return; -fail: - // Option menu fallback for overlong skill lists - FOptionMenuDescriptor *od; - if (desc == NULL) - { - od = new FOptionMenuDescriptor; - if (desc != NULL) delete *desc; - MenuDescriptors[NAME_Skillmenu] = od; - od->mType = MDESC_OptionsMenu; - od->mMenuName = NAME_Skillmenu; - od->mTitle = "$MNU_CHOOSESKILL"; - od->mSelectedItem = 0; - od->mScrollPos = 0; - od->mClass = NULL; - od->mPosition = -15; - od->mScrollTop = 0; - od->mIndent = 160; - od->mDontDim = false; - } - else - { - od = static_cast(*desc); - for(unsigned i=0;imItems.Size(); i++) - { - delete od->mItems[i]; - } - od->mItems.Clear(); - } - for(unsigned int i = 0; i < AllSkills.Size(); i++) - { - FSkillInfo &skill = AllSkills[i]; - FOptionMenuItem *li; - // Using a different name for skills that must be confirmed makes handling this easier. - const char *action = (skill.MustConfirm && !AllEpisodes[gs->Episode].mNoSkill) ? - "StartgameConfirm" : "Startgame"; - - FString *pItemText = NULL; - if (gs->PlayerClass != NULL) - { - pItemText = skill.MenuNamesForPlayerClass.CheckKey(gs->PlayerClass); - } - li = new FOptionMenuItemSubmenu(pItemText? *pItemText : skill.MenuName, action, i); - od->mItems.Push(li); - if (!done) - { - done = true; - od->mSelectedItem = M_GetDefaultSkill(); - } - } -#endif -} - //============================================================================= // // Returns the default skill level. @@ -1324,14 +1238,5 @@ fail: int M_GetDefaultSkill() { -#if 0 - int defskill = DefaultSkill; - if ((unsigned int)defskill >= AllSkills.Size()) - { - defskill = (AllSkills.Size() - 1) / 2; - } - return defskill; -#else - return 1; -#endif + return gDefaultSkill; } diff --git a/source/common/menu/optionmenu.cpp b/source/common/menu/optionmenu.cpp index e8bf2f42d..38dddb82b 100644 --- a/source/common/menu/optionmenu.cpp +++ b/source/common/menu/optionmenu.cpp @@ -310,7 +310,7 @@ bool DOptionMenu::MenuEvent (int mkey, bool fromcontroller) break; case MKEY_Enter: - if (mDesc->mSelectedItem >= 0 && mDesc->mItems[mDesc->mSelectedItem]->Activate()) + if (mDesc->mSelectedItem >= 0 && mDesc->mItems[mDesc->mSelectedItem]->Activate(mDesc->mMenuName)) { return true; } diff --git a/source/common/utility/namedef.h b/source/common/utility/namedef.h index 47e37b7c4..ad19352e1 100644 --- a/source/common/utility/namedef.h +++ b/source/common/utility/namedef.h @@ -34,3 +34,5 @@ xx(CustomSubMenu4) xx(CustomSubMenu5) xx(CustomSubMenu6) xx(CustomSubMenu7) +xx(UsermapMenu) +xx(StartGame) \ No newline at end of file diff --git a/source/duke3d/src/config.cpp b/source/duke3d/src/config.cpp index 886984031..93d36764a 100644 --- a/source/duke3d/src/config.cpp +++ b/source/duke3d/src/config.cpp @@ -74,7 +74,6 @@ void CONFIG_SetDefaults(void) ud.angleinterpolation = 0; ud.camerasprite = -1; ud.config.ShowWeapons = 0; - ud.default_skill = 1; ud.display_bonus_screen = 1; hud_position = 0; diff --git a/source/duke3d/src/d_menu.cpp b/source/duke3d/src/d_menu.cpp index c62f09588..9a3a89449 100644 --- a/source/duke3d/src/d_menu.cpp +++ b/source/duke3d/src/d_menu.cpp @@ -35,6 +35,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #include "gamecontrol.h" #include "c_bind.h" #include "menu/menu.h" +#include "gstrings.h" #include "../../glbackend/glbackend.h" BEGIN_DUKE_NS @@ -44,10 +45,6 @@ BEGIN_DUKE_NS #define MENU_MARGIN_CENTER 160 #define MENU_HEIGHT_CENTER 100 -// This is for intermediate levels in the episode selection chain. Ion Fury uses this. -MenuGameplayStemEntry g_MenuGameplayEntries[MAXMENUGAMEPLAYENTRIES]; -int ME_NEWGAMECUSTOMENTRIES[MAXMENUGAMEPLAYENTRIES]; -int ME_NEWGAMECUSTOMSUBENTRIES[MAXMENUGAMEPLAYENTRIES][MAXMENUGAMEPLAYENTRIES]; enum MenuTextFlags_t { @@ -96,212 +93,6 @@ void Menu_Init(void) MF_Minifont.textflags |= TEXT_UPPERCASE; #if 0 - //int32_t i, j, k; - if (FURY) - { - MMF_Top_Skill.pos.x = (320 << 15); - ME_SKILL_TEMPLATE.format = &MEF_LeftMenu; - } - - // prepare gamefuncs and keys - MEOSN_Gamefuncs[0] = MenuGameFuncNone; - MEOSV_Gamefuncs[0] = -1; - k = 1; - for (i = 0; i < NUMGAMEFUNCTIONS; ++i) - { - MenuGameFuncs[i] = buttonMap.GetButtonAlias(i); - MenuGameFuncs[i].Substitute('_', ' '); - - if (MenuGameFuncs[i][0] != '\0') - { - MEOSN_Gamefuncs[k] = MenuGameFuncs[i]; - MEOSV_Gamefuncs[k] = i; - ++k; - } - } - MEOS_Gamefuncs.numOptions = k; - - for (i = 0; i < NUMKEYS; ++i) - MEOSN_Keys[i] = KB_ScanCodeToString(i); - MEOSN_Keys[NUMKEYS - 1] = MenuKeyNone; - - - // prepare episodes - k = 0; - - if (gVolumeNames[i].IsNotEmpty()) - { - if (!(gVolumeFlags[i] & EF_HIDEFROMSP)) - { - MEL_EPISODE[i] = &ME_EPISODE[i]; - ME_EPISODE[i] = ME_EPISODE_TEMPLATE; - ME_EPISODE[i].name = gVolumeNames[i]; - } - - // if (!(EpisodeFlags[i] & EF_HIDEFROMMP)) - { - MEOSN_NetEpisodes[k] = gVolumeNames[i]; - MEOSV_NetEpisodes[k] = i; - - k++; - } - } - M_EPISODE.numEntries = g_volumeCnt + 2; - - MEL_EPISODE[g_volumeCnt] = &ME_Space4_Redfont; - MEL_EPISODE[g_volumeCnt + 1] = &ME_EPISODE_USERMAP; - MEOSN_NetEpisodes[k] = MenuUserMap; - MEOSV_NetEpisodes[k] = MAXVOLUMES; - - MEOS_NETOPTIONS_EPISODE.numOptions = k + 1; - NetEpisode = MEOSV_NetEpisodes[0]; - MMF_Top_Episode.pos.y = (58 + (3 - k) * 6) << 16; - if (g_skillCnt == 0) - MEO_EPISODE.linkID = MENU_NULL; - M_EPISODE.currentEntry = ud.default_volume; - - // prepare new game custom :O - if (g_MenuGameplayEntries[0].entry.isValid()) - { - MEO_MAIN_NEWGAME.linkID = M_NEWVERIFY.linkID = MENU_NEWGAMECUSTOM; - - int e = 0; - for (MenuGameplayStemEntry const& stem : g_MenuGameplayEntries) - { - MenuGameplayEntry const& entry = stem.entry; - if (!entry.isValid()) - break; - - MenuEntry_t& e_me = ME_NEWGAMECUSTOMENTRIES[e]; - e_me = ME_EPISODE_TEMPLATE; - MenuLink_t& e_meo = MEO_NEWGAMECUSTOM[e]; - e_meo = MEO_NEWGAMECUSTOM_TEMPLATE; - e_me.entry = &e_meo; - - e_me.name = entry.name; - if (entry.flags & MGE_Locked) - e_me.flags |= MEF_Disabled; - if (entry.flags & MGE_Hidden) - e_me.flags |= MEF_Hidden; - - int s = 0; - for (MenuGameplayEntry const& subentry : stem.subentries) - { - if (!subentry.isValid()) - break; - - MenuEntry_t& s_me = ME_NEWGAMECUSTOMSUBENTRIES[e][s]; - s_me = ME_EPISODE_TEMPLATE; - MenuLink_t& s_meo = MEO_NEWGAMECUSTOMSUB[e][s]; - s_meo = MEO_NEWGAMECUSTOMSUB_TEMPLATE; - s_me.entry = &s_meo; - - s_me.name = subentry.name; - if (subentry.flags & MGE_Locked) - s_me.flags |= MEF_Disabled; - if (subentry.flags & MGE_Hidden) - s_me.flags |= MEF_Hidden; - - ++s; - } - - if (entry.flags & MGE_UserContent) - e_meo.linkID = MENU_USERMAP; - else if (s == 0) - e_meo.linkID = MENU_SKILL; - - ++e; - } - - Menu_PopulateNewGameCustom(); - } - - // prepare skills - k = -1; - for (i = 0; i < g_skillCnt && g_skillNames[i][0]; ++i) - { - MEL_SKILL[i] = &ME_SKILL[i]; - ME_SKILL[i] = ME_SKILL_TEMPLATE; - ME_SKILL[i].name = g_skillNames[i]; - - MEOSN_NetSkills[i] = g_skillNames[i]; - - k = i; - } - ++k; - M_SKILL.numEntries = g_skillCnt; // k; - MEOS_NETOPTIONS_MONSTERS.numOptions = g_skillCnt + 1; // k+1; - MEOSN_NetSkills[g_skillCnt] = MenuSkillNone; - MMF_Top_Skill.pos.y = (58 + (4 - g_skillCnt) * 6) << 16; - M_SKILL.currentEntry = ud.default_skill; - Menu_AdjustForCurrentEntryAssignmentBlind(&M_SKILL); - - // prepare multiplayer gametypes - k = -1; - for (i = 0; i < MAXGAMETYPES; ++i) - if (g_gametypeNames[i][0]) - { - MEOSN_NetGametypes[i] = g_gametypeNames[i]; - k = i; - } - ++k; - MEOS_NETOPTIONS_GAMETYPE.numOptions = k; - if (NAM_WW2GI) - ME_NETOPTIONS_MONSTERS.name = "Enemies"; - - // prepare cheats - for (i = 0; i < NUMCHEATFUNCS; ++i) - MEL_CHEATS[i + 1] = &ME_CheatCodes[i]; - - // prepare text chat macros - for (i = 0; i < MAXRIDECULE; ++i) - { - MEL_MACROS[i] = &ME_MACROS[i]; - ME_MACROS[i] = ME_MACROS_TEMPLATE; - ME_MACROS[i].entry = &MEO_MACROS[i]; - MEO_MACROS[i] = MEO_MACROS_TEMPLATE; - - MEO_MACROS[i].variable = sink;// ud.ridecule[i]; temporarily disabled - } - - // prepare input - for (i = 0; i < NUMGAMEFUNCTIONS; ++i) - { - if (MenuGameFuncs[i][0] == '\0') - { - MEL_KEYBOARDSETUPFUNCS[i] = NULL; - continue; - } - - MEL_KEYBOARDSETUPFUNCS[i] = &ME_KEYBOARDSETUPFUNCS[i]; - ME_KEYBOARDSETUPFUNCS[i] = ME_KEYBOARDSETUPFUNCS_TEMPLATE; - ME_KEYBOARDSETUPFUNCS[i].name = MenuGameFuncs[i]; - ME_KEYBOARDSETUPFUNCS[i].entry = &MEO_KEYBOARDSETUPFUNCS[i]; - MEO_KEYBOARDSETUPFUNCS[i] = MEO_KEYBOARDSETUPFUNCS_TEMPLATE; - } - M_KEYBOARDKEYS.numEntries = NUMGAMEFUNCTIONS; - for (i = 0; i < 2 * joystick.numButtons + 8 * joystick.numHats; ++i) - { - if (i < 2 * joystick.numButtons) - { - if (i & 1) - Bsnprintf(MenuJoystickNames[i], MAXJOYBUTTONSTRINGLENGTH, "Double %s", joyGetName(1, i >> 1)); - else - Bstrncpy(MenuJoystickNames[i], joyGetName(1, i >> 1), MAXJOYBUTTONSTRINGLENGTH); - } - else - { - Bsnprintf(MenuJoystickNames[i], MAXJOYBUTTONSTRINGLENGTH, (i & 1) ? "Double Hat %d %s" : "Hat %d %s", ((i - 2 * joystick.numButtons) >> 3), MenuJoystickHatDirections[((i - 2 * joystick.numButtons) >> 1) % 4]); - } - } - for (i = 0; i < joystick.numAxes; ++i) - { - ME_JOYSTICKAXES[i] = ME_JOYSTICKAXES_TEMPLATE; - Bstrncpy(MenuJoystickAxes[i], joyGetName(0, i), MAXJOYBUTTONSTRINGLENGTH); - ME_JOYSTICKAXES[i].name = MenuJoystickAxes[i]; - MEL_JOYSTICKAXES[i] = &ME_JOYSTICKAXES[i]; - } - M_JOYSTICKAXES.numEntries = joystick.numAxes; // prepare sound setup #ifndef EDUKE32_STANDALONE @@ -311,28 +102,6 @@ void Menu_Init(void) ME_SOUND_DUKETALK.name = "Grunt talk:"; #endif - if (FURY) - { - MF_Redfont.between.x = 2 << 16; - MF_Redfont.cursorScale = 32768; - MF_Redfont.zoom = 16384; - MF_Bluefont.zoom = 16384; - - // hack; should swap out pointers - MF_Minifont = MF_Bluefont; - - MMF_Top_Main.pos.x = 40 << 16; - MMF_Top_Main.pos.y = 130 << 16; - MMF_Top_Main.bottomcutoff = 190 << 16; - M_OPTIONS.format = &MMF_Top_Main; - - MEF_MainMenu.width = MEF_OptionsMenu.width = -(160 << 16); - MEF_MainMenu.marginBottom = 7 << 16; - - M_OPTIONS.title = NoTitle; - - SELECTDIR_z = 16384; - } // prepare shareware if (VOLUMEONE) @@ -358,10 +127,7 @@ void Menu_Init(void) M_CREDITS.title = M_CREDITS2.title = M_CREDITS3.title = s_Credits; } - MenuEntry_HideOnCondition(&ME_MAIN_HELP, G_GetLogoFlags() & LOGO_NOHELP); -#ifndef EDUKE32_SIMPLE_MENU - MenuEntry_HideOnCondition(&ME_MAIN_CREDITS, G_GetLogoFlags() & LOGO_NOCREDITS); -#endif + #endif } @@ -374,6 +140,7 @@ static void Menu_DrawTopBar(const vec2_t origin) static void Menu_DrawTopBarCaption(const char *caption, const vec2_t origin) { static char t[64]; + if (*caption == '$') caption = GStrings(caption + 1); size_t const srclen = strlen(caption); size_t const dstlen = min(srclen, ARRAY_SIZE(t)-1); memcpy(t, caption, dstlen); @@ -572,6 +339,11 @@ protected: void PreDraw() override { CallScript(CurrentMenu == this ? EVENT_DISPLAYMENU : EVENT_DISPLAYMENUREST, true); + if (mDesc->mCaption.IsNotEmpty()) + { + Menu_DrawTopBar(origin); + Menu_DrawTopBarCaption(mDesc->mCaption, origin); + } } void PostDraw() override @@ -601,11 +373,6 @@ class MainMenu : public DukeListMenu if (PLUTOPAK) // JBF 20030804 rotatesprite_fs((origin.y << 16) + ((MENU_MARGIN_CENTER+100)<<16), (origin.y << 16) + (36<<16), 65536L,0,PLUTOPAKSPRITE+2,(sintable[((int32_t) totalclock<<4)&2047]>>11),0,2+8); } - else if (mDesc->mCaption.IsNotEmpty()) - { - Menu_DrawTopBar(origin); - Menu_DrawTopBarCaption(mDesc->mCaption, origin); - } } }; diff --git a/source/duke3d/src/game.h b/source/duke3d/src/game.h index eaec48713..f9a7903fe 100644 --- a/source/duke3d/src/game.h +++ b/source/duke3d/src/game.h @@ -161,8 +161,6 @@ typedef struct { int32_t playerbest; - int32_t default_volume, default_skill; - int32_t returnvar[MAX_RETURN_VALUES-1]; uint32_t userbytever; diff --git a/source/duke3d/src/gamestructures.cpp b/source/duke3d/src/gamestructures.cpp index c3bc39c7a..a171b205f 100644 --- a/source/duke3d/src/gamestructures.cpp +++ b/source/duke3d/src/gamestructures.cpp @@ -1508,8 +1508,8 @@ int32_t __fastcall VM_GetUserdef(int32_t labelNum, int const lParm2) case USERDEFS_GLOBAL_R: labelNum = globalr; break; case USERDEFS_GLOBAL_G: labelNum = globalg; break; case USERDEFS_GLOBAL_B: labelNum = globalb; break; - case USERDEFS_DEFAULT_VOLUME: labelNum = ud.default_volume; break; - case USERDEFS_DEFAULT_SKILL: labelNum = ud.default_skill; break; + case USERDEFS_DEFAULT_VOLUME: labelNum = gDefaultVolume; break; + case USERDEFS_DEFAULT_SKILL: labelNum = gDefaultSkill; break; case USERDEFS_MENU_SHADEDESELECTED: labelNum = MF_Redfont.shade_deselected; break; case USERDEFS_MENU_SHADEDISABLED: labelNum = MF_Redfont.shade_disabled; break; case USERDEFS_MENUTEXT_ZOOM: labelNum = MF_Redfont.zoom; break; @@ -1701,8 +1701,8 @@ void __fastcall VM_SetUserdef(int const labelNum, int const lParm2, int32_t cons case USERDEFS_GLOBAL_R: globalr = iSet; break; case USERDEFS_GLOBAL_G: globalg = iSet; break; case USERDEFS_GLOBAL_B: globalb = iSet; break; - case USERDEFS_DEFAULT_VOLUME: ud.default_volume = iSet; break; - case USERDEFS_DEFAULT_SKILL: ud.default_skill = iSet; break; + case USERDEFS_DEFAULT_VOLUME: gDefaultVolume = iSet; break; + case USERDEFS_DEFAULT_SKILL: gDefaultSkill = iSet; break; case USERDEFS_MENU_SHADEDESELECTED: MF_Redfont.shade_deselected = MF_Bluefont.shade_deselected = MF_Minifont.shade_deselected = iSet; break; case USERDEFS_MENU_SHADEDISABLED: MF_Redfont.shade_disabled = MF_Bluefont.shade_disabled = MF_Minifont.shade_disabled = iSet; break; case USERDEFS_MENUTEXT_ZOOM: MF_Redfont.zoom = iSet; break; @@ -1751,16 +1751,8 @@ void __fastcall VM_SetUserdef(int const labelNum, int const lParm2, int32_t cons case USERDEFS_DRAW_Y: rotatesprite_y_offset = iSet; break; case USERDEFS_DRAW_YXASPECT: rotatesprite_yxaspect = iSet; break; case USERDEFS_FOV: r_fov.SetGenericRepDefault(iSet, CVAR_Int); break; - case USERDEFS_NEWGAMECUSTOMOPEN: - for (unsigned int b = 0; b < MAXMENUGAMEPLAYENTRIES; ++b) - if (iSet & (1u<actors_killed, p->max_actors_killed, p->secret_rooms, p->max_secret_rooms, p->player_par / REALGAMETICSPERSEC }; } diff --git a/wadsrc/static/demolition/language.csv b/wadsrc/static/demolition/language.csv index cfd2f5717..55e8b3769 100644 --- a/wadsrc/static/demolition/language.csv +++ b/wadsrc/static/demolition/language.csv @@ -10,4 +10,10 @@ Continue,MNU_CONTINUE,,,,,Fortfahren,,,,,,,,,,,,,,,,, Credits,MNU_CREDITS,,,,,,,,,,,,,,,,,,,,,, Cool Stuff,MNU_COOLSTUFF,,,,,Cooles Zeug,,,,,,,,,,,,,,,,, Multiplayer,MNU_MULTIPLAYER,,,,,Mehrspieler,,,,,,,,,,,,,,,,, -End Game,MNU_ENDGAME,,,,,Spiel beenden,,,,,,,,,,,,,,,,, \ No newline at end of file +End Game,MNU_ENDGAME,,,,,Spiel beenden,,,,,,,,,,,,,,,,, +User Map,MNU_USERMAP,,,,,Benutzerlevel,,,,,,,,,,,,,,,,, +Select a user map to play,MNU_SELECTUSERMAP,,,,,"Wähle ein Level zum Spielen +",,,,,,,,,,,,,,,,, +Select an Episode,MNU_SELECTEPISODE,,,,,"Welche Episode? +",,,,,,,,,,,,,,,,, +Select Skill,MNU_SELECTSKILL,,,,,Schwierigkeitsgrad,,,,,,,,,,,,,,,,, \ No newline at end of file diff --git a/wadsrc/static/demolition/menudef.txt b/wadsrc/static/demolition/menudef.txt index bccda7541..0fa8099b0 100644 --- a/wadsrc/static/demolition/menudef.txt +++ b/wadsrc/static/demolition/menudef.txt @@ -1,6 +1,6 @@ //------------------------------------------------------------------------------------------- // -// Text only variant of the main menu for Doom, Strife and Chex Quest to be used with localized content. +// // //------------------------------------------------------------------------------------------- @@ -20,7 +20,7 @@ LISTMENU "MainMenu" centermenu } class "Duke.MainMenu" - NativeTextItem "$MNU_NEWGAME", "n", "PlayerclassMenu" + NativeTextItem "$MNU_NEWGAME", "n", "EpisodeMenu" //NativeTextItem "$MNU_NEWGAME", "m", "MultiMenu" // In EDuke this replaces "New Game" when in networking mode. Kept here as a reminder (I'm not going to support EDuke's C/S implementation) ifgame(fury) { @@ -37,8 +37,7 @@ LISTMENU "MainMenu" } ifgame(Redneck, RedneckRides) { - linespacing 15 - NativeTextItem "$MNU_NEWGAME", "n", "PlayerclassMenu" + NativeTextItem "$MNU_NEWGAME", "n", "EpisodeMenu" //NativeTextItem "$MNU_NEWGAME", "m", "MultiMenu" // In EDuke this replaces "New Game" when in networking mode. Kept here as a reminder (I'm not going to support EDuke's C/S implementation) NativeTextItem "$MNU_LOADGAME", "l", "LoadGameMenu" NativeTextItem "$MNU_OPTIONS", "o", "OptionsMenu" @@ -48,8 +47,7 @@ LISTMENU "MainMenu" } ifgame(Blood) { - linespacing 15 - NativeTextItem "$MNU_NEWGAME", "n", "PlayerclassMenu" + NativeTextItem "$MNU_NEWGAME", "n", "EpisodeMenu" NativeTextItem "$MNU_MULTIPLAYER", "m", "MultiMenu" NativeTextItem "$MNU_OPTIONS", "o", "OptionsMenu" NativeTextItem "$MNU_LOADGAME", "l", "LoadGameMenu" @@ -59,8 +57,7 @@ LISTMENU "MainMenu" } ifgame(ShadowWarrior) { - linespacing 15 - NativeTextItem "$MNU_NEWGAME", "n", "PlayerclassMenu" + NativeTextItem "$MNU_NEWGAME", "n", "EpisodeMenu" NativeTextItem "$MNU_LOADGAME", "l", "LoadGameMenu" NativeTextItem "$MNU_SAVEGAME", "s", "SaveGameMenu" NativeTextItem "$MNU_OPTIONS", "o", "OptionsMenu" @@ -87,7 +84,7 @@ LISTMENU "IngameMenu" } linespacing 15 class "Duke.MainMenu" - NativeTextItem "$MNU_NEWGAME", "n", "CustomGameMenu" + NativeTextItem "$MNU_NEWGAME", "n", "EpisodeMenu" NativeTextItem "$MNU_SAVEGAME", "s", "SaveGameMenu" NativeTextItem "$MNU_LOADGAME", "l", "LoadGameMenu" NativeTextItem "$MNU_OPTIONS", "o", "OptionsMenu" @@ -131,68 +128,100 @@ LISTMENU "IngameMenu" LISTMENU "EpisodeMenu" { - ScriptId 100 - // Episode names filled in programmatically - //Spacer - /* - NativeTextItem "$MNU_USERMAP", "u", "UserMap" - ifgame(ShadowWarrior) + ifgame(Duke, Nam, WW2GI, Fury) // Ion Fury does not use this menu. { - NativeStaticTextItem "$MNU_SELECTUSERMAP" + caption "$MNU_SELECTEPISODE" + position 160, 48, 142 + centermenu + fixedspacing 5 + class "Duke.ListMenu" } - */ + + ScriptId 100 } LISTMENU "SkillMenu" { - NativeTextItem "1", "", "StartGame", 1 + ifgame(Duke, Nam, WW2GI, Fury) // Ion Fury does not use this menu. + { + caption "$MNU_SELECTSKILL" + position 160, 55, 135 + centermenu + fixedspacing 5 + class "Duke.ListMenu" + } + + ScriptId 110 } LISTMENU "CustomGameMenu" { + position 160, 48, 142 + centermenu + fixedspacing 5 ScriptId 102 - // Filled in programmatically - //NativeTextItem "1", "", "CustomSubMenu1" } LISTMENU "CustomSubMenu1" { + position 160, 48, 142 + centermenu + fixedspacing 5 ScriptId 103 - //NativeTextItem "1", "", "SkillMenu" } LISTMENU "CustomSubMenu2" { + position 160, 48, 142 + centermenu + fixedspacing 5 ScriptId 103 - //NativeTextItem "1", "", "SkillMenu" } LISTMENU "CustomSubMenu3" { + position 160, 48, 142 + centermenu + fixedspacing 5 ScriptId 103 - //NativeTextItem "1", "", "SkillMenu" } LISTMENU "CustomSubMenu4" { + position 160, 48, 142 + centermenu + fixedspacing 5 ScriptId 103 - //NativeTextItem "1", "", "SkillMenu" } LISTMENU "CustomSubMenu5" { + position 160, 48, 142 + centermenu + fixedspacing 5 ScriptId 103 - //NativeTextItem "1", "", "SkillMenu" } LISTMENU "CustomSubMenu6" { + position 160, 48, 142 + centermenu + fixedspacing 5 ScriptId 103 - //NativeTextItem "1", "", "SkillMenu" } LISTMENU "CustomSubMenu7" { + position 160, 48, 142 + centermenu + fixedspacing 5 ScriptId 103 - //NativeTextItem "1", "", "SkillMenu" +} + +LISTMENU "MultiMenu" +{ + Caption "$MNU_NETWORKGAME" + NativeTextItem "$MNU_PLAYERSETUP", "p", "PlayerSetupMenu" + NativeTextItem "$MNU_JOINGAME", "j", "JoinGameMenu" + NativeTextItem "$MNU_HOSTGAME", "h", "HostGameMenu" }