diff --git a/src/menu/menu.cpp b/src/menu/menu.cpp index 4ea76fe65..8893b5769 100644 --- a/src/menu/menu.cpp +++ b/src/menu/menu.cpp @@ -1121,7 +1121,7 @@ DEFINE_FIELD(FOptionMenuSettings, mLinespacing) struct IJoystickConfig; // These functions are used by dynamic menu creation. -DMenuItemBase * CreateOptionMenuItemStaticText(const char *name, bool v) +DMenuItemBase * CreateOptionMenuItemStaticText(const char *name, int v) { auto c = PClass::FindClass("OptionMenuItemStaticText"); auto p = c->CreateNew(); diff --git a/src/menu/menu.h b/src/menu/menu.h index d5dd0d5c4..d5211421f 100644 --- a/src/menu/menu.h +++ b/src/menu/menu.h @@ -109,6 +109,7 @@ extern FSavegameManager savegameManager; class DMenu; extern DMenu *CurrentMenu; extern int MenuTime; +class DMenuItemBase; //============================================================================= // @@ -125,18 +126,17 @@ public: FString mNetgameMessage; PClass *mClass = nullptr; bool mProtected = false; + TArray mItems; virtual size_t PropagateMark() { return 0; } }; -class DMenuItemBase; class DListMenuDescriptor : public DMenuDescriptor { DECLARE_CLASS(DListMenuDescriptor, DMenuDescriptor) public: - TArray mItems; int mSelectedItem; double mSelectOfsX; double mSelectOfsY; @@ -187,7 +187,6 @@ class DOptionMenuDescriptor : public DMenuDescriptor DECLARE_CLASS(DOptionMenuDescriptor, DMenuDescriptor) public: - TArray mItems; FString mTitle; int mSelectedItem; int mDrawTop; @@ -350,7 +349,7 @@ void M_MarkMenus(); struct IJoystickConfig; -DMenuItemBase * CreateOptionMenuItemStaticText(const char *name, bool v); +DMenuItemBase * CreateOptionMenuItemStaticText(const char *name, int v = -1); DMenuItemBase * CreateOptionMenuItemSubmenu(const char *label, FName cmd, int center); DMenuItemBase * CreateOptionMenuItemControl(const char *label, FName cmd, FKeyBindings *bindings); DMenuItemBase * CreateOptionMenuItemJoyConfigMenu(const char *label, IJoystickConfig *joy); diff --git a/src/menu/menudef.cpp b/src/menu/menudef.cpp index 504fd9785..e3184d316 100644 --- a/src/menu/menudef.cpp +++ b/src/menu/menudef.cpp @@ -516,6 +516,52 @@ static bool CheckCompatible(DMenuDescriptor *newd, DMenuDescriptor *oldd) return newd->mClass->IsDescendantOf(oldd->mClass); } +static int GetGroup(DMenuItemBase *desc) +{ + if (desc->IsKindOf(NAME_OptionMenuItemCommand)) return 2; + if (desc->IsKindOf(NAME_OptionMenuItemSubmenu)) return 1; + if (desc->IsKindOf(NAME_OptionMenuItemControlBase)) return 3; + if (desc->IsKindOf(NAME_OptionMenuItemOptionBase)) return 4; + if (desc->IsKindOf(NAME_OptionMenuSliderBase)) return 4; + if (desc->IsKindOf(NAME_OptionMenuFieldBase)) return 4; + if (desc->IsKindOf(NAME_OptionMenuItemColorPicker)) return 4; + if (desc->IsKindOf(NAME_OptionMenuItemStaticText)) return 5; + if (desc->IsKindOf(NAME_OptionMenuItemStaticTextSwitchable)) return 5; + return 0; +} + +static bool FindMatchingItem(DMenuItemBase *desc) +{ + int grp = GetGroup(desc); + if (grp == 0) return false; // no idea what this is. + if (grp == 5) return true; // static texts always match + + FName name = desc->mAction; + if (grp == 1) + { + // Check for presence of menu + auto menu = MenuDescriptors.CheckKey(name); + if (menu == nullptr) return true; + } + else if (grp == 4) + { + // Check for presence of CVAR and blacklist + auto cv = GetCVar(nullptr, name.GetChars()); + if (cv == nullptr) return true; + } + + MenuDescriptorList::Iterator it(MenuDescriptors); + MenuDescriptorList::Pair *pair; + while (it.NextPair(pair)) + { + for (auto it : pair->Value->mItems) + { + if (it->mAction == name && GetGroup(it) == grp) return true; + } + } + return false; +} + static bool ReplaceMenu(FScanner &sc, DMenuDescriptor *desc) { DMenuDescriptor **pOld = MenuDescriptors.CheckKey(desc->mMenuName); @@ -523,7 +569,32 @@ static bool ReplaceMenu(FScanner &sc, DMenuDescriptor *desc) { if ((*pOld)->mProtected) { - sc.ScriptMessage("Cannot replace protected menu %s!", desc->mMenuName.GetChars()); + // If this tries to replace an option menu with an option menu, let's append all new entries to the old menu. + // Otherwise bail out because for list menus it's not that simple. + if (desc->IsKindOf(RUNTIME_CLASS(DListMenuDescriptor)) || (*pOld)->IsKindOf(RUNTIME_CLASS(DListMenuDescriptor))) + { + sc.ScriptMessage("Cannot replace protected menu %s.", desc->mMenuName.GetChars()); + return true; + } + for (int i = desc->mItems.Size()-1; i >= 0; i--) + { + if (FindMatchingItem(desc->mItems[i])) + { + desc->mItems.Delete(i); + } + } + if (desc->mItems.Size() > 0) + { + auto sep = CreateOptionMenuItemStaticText("---------------", CR_YELLOW); + (*pOld)->mItems.Push(sep); + for (auto it : desc->mItems) + { + (*pOld)->mItems.Push(it); + } + desc->mItems.Clear(); + + sc.ScriptMessage("Merged %d items into %s", desc->mItems.Size(), desc->mMenuName.GetChars()); + } return true; } @@ -1400,9 +1471,9 @@ static void InitKeySections() for (unsigned i = 0; i < KeySections.Size(); i++) { FKeySection *sect = &KeySections[i]; - DMenuItemBase *item = CreateOptionMenuItemStaticText(" ", false); + DMenuItemBase *item = CreateOptionMenuItemStaticText(" "); menu->mItems.Push(item); - item = CreateOptionMenuItemStaticText(sect->mTitle, true); + item = CreateOptionMenuItemStaticText(sect->mTitle, 1); menu->mItems.Push(item); for (unsigned j = 0; j < sect->mActions.Size(); j++) { diff --git a/src/namedef.h b/src/namedef.h index cc3e65088..3d8fd684f 100644 --- a/src/namedef.h +++ b/src/namedef.h @@ -927,3 +927,14 @@ xx(MessageBoxMenu) xx(Both) xx(Physical) xx(Visual) + +xx(OptionMenuItemSubmenu) +xx(OptionMenuItemCommand) +xx(OptionMenuItemControlBase) +xx(OptionMenuItemOptionBase) +xx(OptionMenuSliderBase) +xx(OptionMenuFieldBase) +xx(OptionMenuItemColorPicker) +xx(OptionMenuItemStaticText) +xx(OptionMenuItemStaticTextSwitchable) +xx(mAction)