diff --git a/install/games/darkmod.game b/install/games/darkmod.game index 62c4f1c624..9a35f0d673 100644 --- a/install/games/darkmod.game +++ b/install/games/darkmod.game @@ -37,6 +37,11 @@ + + + hot_reload + + diff --git a/install/menu.xml b/install/menu.xml index 0aabe20ca6..12c30f6a91 100644 --- a/install/menu.xml +++ b/install/menu.xml @@ -244,8 +244,8 @@ - - + + diff --git a/radiant/ui/menu/MenuElement.cpp b/radiant/ui/menu/MenuElement.cpp index 7ef7fa45ed..550d611854 100644 --- a/radiant/ui/menu/MenuElement.cpp +++ b/radiant/ui/menu/MenuElement.cpp @@ -2,6 +2,7 @@ #include "i18n.h" #include "itextstream.h" +#include "igame.h" #include "string/split.h" #include "string/join.h" @@ -200,62 +201,67 @@ void MenuElement::setAccelerator(const std::string& accelStr) MenuElementPtr MenuElement::CreateFromNode(const xml::Node& node) { - MenuElementPtr item; - - std::string nodeName = node.getName(); - - if (nodeName == "menuItem") - { - item = std::make_shared(); - - // Get the EventPtr according to the event - item->setEvent(node.getAttributeValue("command")); - item->setIcon(node.getAttributeValue("icon")); - } - else if (nodeName == "menuSeparator") - { - item = std::make_shared(); - } - else if (nodeName == "subMenu") - { - item = std::make_shared(); - } - else if (nodeName == "menu") - { - item = std::make_shared(); - } - else - { - rError() << "MenuElement: Unknown node found: " << node.getName() << std::endl; - return item; - } - - item->setName(node.getAttributeValue("name")); - - // Put the caption through gettext before passing it to setCaption - item->setCaption(_(node.getAttributeValue("caption").c_str())); - - // Parse subnodes - xml::NodeList childNodes = node.getChildren(); - - for (const xml::Node& childNode : childNodes) - { - if (childNode.getName() == "text" || childNode.getName() == "comment") - { - continue; - } - - // Allocate a new child item - MenuElementPtr childItem = CreateFromNode(childNode); - - // Add the child to the list - if (childItem) - { - item->addChild(childItem); - } - } - - return item; + MenuElementPtr item; + + // Don't create anything if the menu item requires a non-existent game feature + if (auto feature = node.getAttributeValue("gamefeature"); + !feature.empty() && !GlobalGameManager().currentGame()->hasFeature(feature)) + { + return {}; + } + + std::string nodeName = node.getName(); + + if (nodeName == "menuItem") + { + item = std::make_shared(); + + // Get the EventPtr according to the event + item->setEvent(node.getAttributeValue("command")); + item->setIcon(node.getAttributeValue("icon")); + } + else if (nodeName == "menuSeparator") + { + item = std::make_shared(); + } + else if (nodeName == "subMenu") + { + item = std::make_shared(); + } + else if (nodeName == "menu") + { + item = std::make_shared(); + } + else + { + rError() << "MenuElement: Unknown node found: " << node.getName() << std::endl; + return item; + } + + item->setName(node.getAttributeValue("name")); + + // Put the caption through gettext before passing it to setCaption + item->setCaption(_(node.getAttributeValue("caption").c_str())); + + // Parse subnodes + xml::NodeList childNodes = node.getChildren(); + + for (const xml::Node& childNode : childNodes) + { + if (childNode.getName() == "text" || childNode.getName() == "comment") + { + continue; + } + + // Allocate a new child item + MenuElementPtr childItem = CreateFromNode(childNode); + + // Add the child to the list + if (childItem) + item->addChild(childItem); + } + + return item; } MenuElementPtr MenuElement::CreateForType(ItemType type) diff --git a/radiant/ui/menu/MenuElement.h b/radiant/ui/menu/MenuElement.h index 4f341cb63a..26575ef815 100644 --- a/radiant/ui/menu/MenuElement.h +++ b/radiant/ui/menu/MenuElement.h @@ -126,11 +126,17 @@ class MenuElement : // Tries to (recursively) locate the MenuElement by looking up the path MenuElementPtr find(const std::string& menuPath); - /** - * Parses the given XML node recursively and creates all items from the - * information it finds. Returns the constructed MenuElement. - */ - static MenuElementPtr CreateFromNode(const xml::Node& node); + /** + * @brief Parses the given XML node recursively and creates all items from the + * information it finds. + * + * @param node + * XML node containing the menu information. + * + * @return Constructed MenuElement pointer or null if the menu item is not required in + * the current game configuration. + */ + static MenuElementPtr CreateFromNode(const xml::Node& node); /** * Constructs a menu element for the given type diff --git a/test/Game.cpp b/test/Game.cpp index c001d97865..daecb329c9 100644 --- a/test/Game.cpp +++ b/test/Game.cpp @@ -64,6 +64,10 @@ TEST_F(GameTest, GetOptionalFeatures) // Only Quake 3 should have the "detail_brushes" feature EXPECT_FALSE((*tdm)->hasFeature("detail_brushes")); EXPECT_TRUE((*q3)->hasFeature("detail_brushes")); + + // Only Dark Mod should have the "hot_reload" feature + EXPECT_TRUE((*tdm)->hasFeature("hot_reload")); + EXPECT_FALSE((*q3)->hasFeature("hot_reload")); } } \ No newline at end of file