diff --git a/Mods/vcmi/config/vcmi/english.json b/Mods/vcmi/config/vcmi/english.json index ce9bc9e45..c0f7f1ddc 100644 --- a/Mods/vcmi/config/vcmi/english.json +++ b/Mods/vcmi/config/vcmi/english.json @@ -15,6 +15,8 @@ "vcmi.adventureMap.monsterLevel" : "\n\nLevel %LEVEL %TOWN %ATTACK_TYPE unit", "vcmi.adventureMap.monsterMeleeType" : "melee", "vcmi.adventureMap.monsterRangedType" : "ranged", + "vcmi.adventureMap.search.hover" : "Search map object", + "vcmi.adventureMap.search.help" : "Select object to search on map.", "vcmi.adventureMap.confirmRestartGame" : "Are you sure you want to restart the game?", "vcmi.adventureMap.noTownWithMarket" : "There are no available marketplaces!", diff --git a/Mods/vcmi/config/vcmi/german.json b/Mods/vcmi/config/vcmi/german.json index 24952b3f4..5d6abf103 100644 --- a/Mods/vcmi/config/vcmi/german.json +++ b/Mods/vcmi/config/vcmi/german.json @@ -15,6 +15,8 @@ "vcmi.adventureMap.monsterLevel" : "\n\nStufe %LEVEL %TOWN-Einheit (%ATTACK_TYPE)", "vcmi.adventureMap.monsterMeleeType" : "Nahkampf", "vcmi.adventureMap.monsterRangedType" : "Fernkampf", + "vcmi.adventureMap.search.hover" : "Suche Kartenobjekt", + "vcmi.adventureMap.search.help" : "Wähle Objekt das gesucht werden soll.", "vcmi.adventureMap.confirmRestartGame" : "Seid Ihr sicher, dass Ihr das Spiel neu starten wollt?", "vcmi.adventureMap.noTownWithMarket" : "Kein Marktplatz verfügbar!", diff --git a/client/adventureMap/AdventureMapShortcuts.cpp b/client/adventureMap/AdventureMapShortcuts.cpp index fe7253cb0..d5c5ce321 100644 --- a/client/adventureMap/AdventureMapShortcuts.cpp +++ b/client/adventureMap/AdventureMapShortcuts.cpp @@ -24,6 +24,7 @@ #include "../windows/CKingdomInterface.h" #include "../windows/CSpellWindow.h" #include "../windows/CMarketWindow.h" +#include "../windows/GUIClasses.h" #include "../windows/settings/SettingsMainWindow.h" #include "AdventureMapInterface.h" #include "AdventureOptions.h" @@ -36,11 +37,14 @@ #include "../../lib/mapObjects/CGTownInstance.h" #include "../../lib/mapping/CMap.h" #include "../../lib/pathfinder/CGPathNode.h" +#include "../../lib/mapObjectConstructors/CObjectClassesHandler.h" AdventureMapShortcuts::AdventureMapShortcuts(AdventureMapInterface & owner) : owner(owner) , state(EAdventureState::NOT_INITIALIZED) , mapLevel(0) + , searchLast("") + , searchPos(0) {} void AdventureMapShortcuts::setState(EAdventureState newState) @@ -109,7 +113,9 @@ std::vector AdventureMapShortcuts::getShortcuts() { EShortcut::ADVENTURE_MOVE_HERO_EE, optionHeroSelected(), [this]() { this->moveHeroDirectional({+1, 0}); } }, { EShortcut::ADVENTURE_MOVE_HERO_NW, optionHeroSelected(), [this]() { this->moveHeroDirectional({-1, -1}); } }, { EShortcut::ADVENTURE_MOVE_HERO_NN, optionHeroSelected(), [this]() { this->moveHeroDirectional({ 0, -1}); } }, - { EShortcut::ADVENTURE_MOVE_HERO_NE, optionHeroSelected(), [this]() { this->moveHeroDirectional({+1, -1}); } } + { EShortcut::ADVENTURE_MOVE_HERO_NE, optionHeroSelected(), [this]() { this->moveHeroDirectional({+1, -1}); } }, + { EShortcut::ADVENTURE_SEARCH, optionSidePanelActive(),[this]() { this->search(false); } }, + { EShortcut::ADVENTURE_SEARCH_CONTINUE, optionSidePanelActive(),[this]() { this->search(true); } } }; return result; } @@ -457,6 +463,62 @@ void AdventureMapShortcuts::zoom( int distance) owner.hotkeyZoom(distance, false); } +void AdventureMapShortcuts::search(bool next) +{ + // get all relevant objects + std::vector visitableObjInstances; + for(auto & obj : LOCPLINT->cb->getAllVisitableObjs()) + if(obj->ID != MapObjectID::MONSTER && obj->ID != MapObjectID::HERO && obj->ID != MapObjectID::TOWN) + visitableObjInstances.push_back(obj->id); + + // count of elements for each group (map is already sorted) + std::map mapObjCount; + for(auto & obj : visitableObjInstances) + mapObjCount[{ LOCPLINT->cb->getObjInstance(obj)->getObjectName() }]++; + + // convert to vector for indexed access + std::vector> textCountList; + for (auto itr = mapObjCount.begin(); itr != mapObjCount.end(); ++itr) + textCountList.push_back(*itr); + + // get pos of last selection + int lastSel = 0; + for(int i = 0; i < textCountList.size(); i++) + if(textCountList[i].first == searchLast) + lastSel = i; + + // create texts + std::vector texts; + for(auto & obj : textCountList) + texts.push_back(obj.first + " (" + std::to_string(obj.second) + ")"); + + // function to center element from list on map + auto selectObjOnMap = [this, textCountList, visitableObjInstances](int index) + { + auto selObj = textCountList[index].first; + + // filter for matching objects + std::vector selVisitableObjInstances; + for(auto & obj : visitableObjInstances) + if(selObj == LOCPLINT->cb->getObjInstance(obj)->getObjectName()) + selVisitableObjInstances.push_back(obj); + + if(searchPos + 1 < selVisitableObjInstances.size() && searchLast == selObj) + searchPos++; + else + searchPos = 0; + + auto objInst = LOCPLINT->cb->getObjInstance(selVisitableObjInstances[searchPos]); + owner.centerOnObject(objInst); + searchLast = objInst->getObjectName(); + }; + + if(next) + selectObjOnMap(lastSel); + else + GH.windows().createAndPushWindow(texts, nullptr, CGI->generaltexth->translate("vcmi.adventureMap.search.hover"), CGI->generaltexth->translate("vcmi.adventureMap.search.help"), [selectObjOnMap](int index){ selectObjOnMap(index); }, lastSel, std::vector>(), true); +} + void AdventureMapShortcuts::nextObject() { const CGHeroInstance *h = LOCPLINT->localState->getCurrentHero(); diff --git a/client/adventureMap/AdventureMapShortcuts.h b/client/adventureMap/AdventureMapShortcuts.h index b32f3ea29..b314e7bbd 100644 --- a/client/adventureMap/AdventureMapShortcuts.h +++ b/client/adventureMap/AdventureMapShortcuts.h @@ -33,6 +33,9 @@ class AdventureMapShortcuts EAdventureState state; int mapLevel; + std::string searchLast; + int searchPos; + void showOverview(); void worldViewBack(); void worldViewScale1x(); @@ -71,6 +74,7 @@ class AdventureMapShortcuts void nextTown(); void nextObject(); void zoom( int distance); + void search(bool next); void moveHeroDirectional(const Point & direction); public: diff --git a/client/gui/Shortcut.h b/client/gui/Shortcut.h index 66019bd77..bd8c57a26 100644 --- a/client/gui/Shortcut.h +++ b/client/gui/Shortcut.h @@ -161,6 +161,8 @@ enum class EShortcut ADVENTURE_RESTART_GAME, ADVENTURE_TO_MAIN_MENU, ADVENTURE_QUIT_GAME, + ADVENTURE_SEARCH, + ADVENTURE_SEARCH_CONTINUE, // Move hero one tile in specified direction. Bound to cursors & numpad buttons ADVENTURE_MOVE_HERO_SW, diff --git a/client/gui/ShortcutHandler.cpp b/client/gui/ShortcutHandler.cpp index a4cffb8c4..19c3cc728 100644 --- a/client/gui/ShortcutHandler.cpp +++ b/client/gui/ShortcutHandler.cpp @@ -209,6 +209,8 @@ EShortcut ShortcutHandler::findShortcut(const std::string & identifier ) const {"adventureZoomIn", EShortcut::ADVENTURE_ZOOM_IN }, {"adventureZoomOut", EShortcut::ADVENTURE_ZOOM_OUT }, {"adventureZoomReset", EShortcut::ADVENTURE_ZOOM_RESET }, + {"adventureSearch", EShortcut::ADVENTURE_SEARCH }, + {"adventureSearchContinue", EShortcut::ADVENTURE_SEARCH_CONTINUE }, {"battleToggleHeroesStats", EShortcut::BATTLE_TOGGLE_HEROES_STATS}, {"battleToggleQueue", EShortcut::BATTLE_TOGGLE_QUEUE }, {"battleUseCreatureSpell", EShortcut::BATTLE_USE_CREATURE_SPELL }, diff --git a/client/widgets/ObjectLists.cpp b/client/widgets/ObjectLists.cpp index d2ad5c1ae..d9a257923 100644 --- a/client/widgets/ObjectLists.cpp +++ b/client/widgets/ObjectLists.cpp @@ -185,6 +185,9 @@ void CListBox::scrollTo(size_t which) //scroll down else if (first + items.size() <= which && which < totalSize) moveToPos(which - items.size() + 1); + + if(slider) + slider->scrollTo(which); } void CListBox::moveToPos(size_t which) diff --git a/client/windows/GUIClasses.cpp b/client/windows/GUIClasses.cpp index 6ab161371..ad630a77d 100644 --- a/client/windows/GUIClasses.cpp +++ b/client/windows/GUIClasses.cpp @@ -1480,39 +1480,47 @@ void CObjectListWindow::CItem::showPopupWindow(const Point & cursorPosition) parent->onPopup(index); } -CObjectListWindow::CObjectListWindow(const std::vector & _items, std::shared_ptr titleWidget_, std::string _title, std::string _descr, std::function Callback, size_t initialSelection, std::vector> images) +CObjectListWindow::CObjectListWindow(const std::vector & _items, std::shared_ptr titleWidget_, std::string _title, std::string _descr, std::function Callback, size_t initialSelection, std::vector> images, bool searchBoxEnabled) : CWindowObject(PLAYER_COLORED, ImagePath::builtin("TPGATE")), onSelect(Callback), selected(initialSelection), images(images) { OBJECT_CONSTRUCTION; + + addUsedEvents(KEYBOARD); + items.reserve(_items.size()); for(int id : _items) - { items.push_back(std::make_pair(id, LOCPLINT->cb->getObjInstance(ObjectInstanceID(id))->getObjectName())); - } + itemsVisible = items; - init(titleWidget_, _title, _descr); + init(titleWidget_, _title, _descr, searchBoxEnabled); + list->scrollTo(initialSelection - 4); // -4 is for centering (list have 9 elements) } -CObjectListWindow::CObjectListWindow(const std::vector & _items, std::shared_ptr titleWidget_, std::string _title, std::string _descr, std::function Callback, size_t initialSelection, std::vector> images) +CObjectListWindow::CObjectListWindow(const std::vector & _items, std::shared_ptr titleWidget_, std::string _title, std::string _descr, std::function Callback, size_t initialSelection, std::vector> images, bool searchBoxEnabled) : CWindowObject(PLAYER_COLORED, ImagePath::builtin("TPGATE")), onSelect(Callback), selected(initialSelection), images(images) { OBJECT_CONSTRUCTION; + + addUsedEvents(KEYBOARD); + items.reserve(_items.size()); for(size_t i=0; i<_items.size(); i++) items.push_back(std::make_pair(int(i), _items[i])); + itemsVisible = items; - init(titleWidget_, _title, _descr); + init(titleWidget_, _title, _descr, searchBoxEnabled); + list->scrollTo(initialSelection - 4); // -4 is for centering (list have 9 elements) } -void CObjectListWindow::init(std::shared_ptr titleWidget_, std::string _title, std::string _descr) +void CObjectListWindow::init(std::shared_ptr titleWidget_, std::string _title, std::string _descr, bool searchBoxEnabled) { titleWidget = titleWidget_; @@ -1527,24 +1535,51 @@ void CObjectListWindow::init(std::shared_ptr titleWidget_, std::stri titleWidget->pos.y =75 + pos.y - titleWidget->pos.h/2; } list = std::make_shared(std::bind(&CObjectListWindow::genItem, this, _1), - Point(14, 151), Point(0, 25), 9, items.size(), 0, 1, Rect(262, -32, 256, 256) ); + Point(14, 151), Point(0, 25), 9, itemsVisible.size(), 0, 1, Rect(262, -32, 256, 256) ); list->setRedrawParent(true); ok = std::make_shared(Point(15, 402), AnimationPath::builtin("IOKAY.DEF"), CButton::tooltip(), std::bind(&CObjectListWindow::elementSelected, this), EShortcut::GLOBAL_ACCEPT); ok->block(!list->size()); + + if(!searchBoxEnabled) + return; + + Rect r(50, 90, pos.w - 100, 16); + const ColorRGBA rectangleColor = ColorRGBA(0, 0, 0, 75); + const ColorRGBA borderColor = ColorRGBA(128, 100, 75); + const ColorRGBA grayedColor = ColorRGBA(158, 130, 105); + searchBoxRectangle = std::make_shared(r.resize(1), rectangleColor, borderColor); + searchBoxDescription = std::make_shared(r.center().x, r.center().y, FONT_SMALL, ETextAlignment::CENTER, grayedColor, CGI->generaltexth->translate("vcmi.spellBook.search")); + + searchBox = std::make_shared(r, FONT_SMALL, ETextAlignment::CENTER, true); + searchBox->setCallback([this](const std::string & text){ + searchBoxDescription->setEnabled(text.empty()); + + itemsVisible.clear(); + for(auto & item : items) + if(boost::algorithm::contains(boost::algorithm::to_lower_copy(item.second), boost::algorithm::to_lower_copy(text))) + itemsVisible.push_back(item); + + selected = 0; + list->resize(itemsVisible.size()); + list->scrollTo(0); + ok->block(!itemsVisible.size()); + + redraw(); + }); } std::shared_ptr CObjectListWindow::genItem(size_t index) { - if(index < items.size()) - return std::make_shared(this, index, items[index].second); + if(index < itemsVisible.size()) + return std::make_shared(this, index, itemsVisible[index].second); return std::shared_ptr(); } void CObjectListWindow::elementSelected() { std::function toCall = onSelect;//save - int where = items[selected].first; //required variables + int where = itemsVisible[selected].first; //required variables close();//then destroy window toCall(where);//and send selected object } @@ -1600,14 +1635,14 @@ void CObjectListWindow::keyPressed(EShortcut key) sel = 0; break; case EShortcut::MOVE_LAST: - sel = static_cast(items.size()); + sel = static_cast(itemsVisible.size()); break; default: return; } - vstd::abetween(sel, 0, items.size()-1); - list->scrollTo(sel); + vstd::abetween(sel, 0, itemsVisible.size()-1); + list->scrollTo(sel - 4); // -4 is for centering (list have 9 elements) changeSelection(sel); } diff --git a/client/windows/GUIClasses.h b/client/windows/GUIClasses.h index 883c812db..fb3740a02 100644 --- a/client/windows/GUIClasses.h +++ b/client/windows/GUIClasses.h @@ -45,6 +45,7 @@ class IImage; class VideoWidget; class VideoWidgetOnce; class GraphicalPrimitiveCanvas; +class TransparentFilledRectangle; enum class EUserEvent; @@ -186,9 +187,14 @@ class CObjectListWindow : public CWindowObject std::shared_ptr ok; std::shared_ptr exit; - std::vector< std::pair > items;//all items present in list + std::shared_ptr searchBox; + std::shared_ptr searchBoxRectangle; + std::shared_ptr searchBoxDescription; - void init(std::shared_ptr titleWidget_, std::string _title, std::string _descr); + std::vector< std::pair > items; //all items present in list + std::vector< std::pair > itemsVisible; //visible items present in list + + void init(std::shared_ptr titleWidget_, std::string _title, std::string _descr, bool searchBoxEnabled); void exitPressed(); public: size_t selected;//index of currently selected item @@ -200,8 +206,8 @@ public: /// Callback will be called when OK button is pressed, returns id of selected item. initState = initially selected item /// Image can be nullptr ///item names will be taken from map objects - CObjectListWindow(const std::vector &_items, std::shared_ptr titleWidget_, std::string _title, std::string _descr, std::function Callback, size_t initialSelection = 0, std::vector> images = {}); - CObjectListWindow(const std::vector &_items, std::shared_ptr titleWidget_, std::string _title, std::string _descr, std::function Callback, size_t initialSelection = 0, std::vector> images = {}); + CObjectListWindow(const std::vector &_items, std::shared_ptr titleWidget_, std::string _title, std::string _descr, std::function Callback, size_t initialSelection = 0, std::vector> images = {}, bool searchBoxEnabled = false); + CObjectListWindow(const std::vector &_items, std::shared_ptr titleWidget_, std::string _title, std::string _descr, std::function Callback, size_t initialSelection = 0, std::vector> images = {}, bool searchBoxEnabled = false); std::shared_ptr genItem(size_t index); void elementSelected();//call callback and close this window diff --git a/config/shortcutsConfig.json b/config/shortcutsConfig.json index 41ee66f22..e6c91b12c 100644 --- a/config/shortcutsConfig.json +++ b/config/shortcutsConfig.json @@ -56,6 +56,8 @@ "adventureZoomIn": "Keypad +", "adventureZoomOut": "Keypad -", "adventureZoomReset": "Backspace", + "adventureSearch": "Ctrl+F", + "adventureSearchContinue": "Alt+F", "battleAutocombat": "A", "battleAutocombatEnd": "Q", "battleCastSpell": "C", diff --git a/lib/CGameInfoCallback.cpp b/lib/CGameInfoCallback.cpp index b6017df9d..112f37fcd 100644 --- a/lib/CGameInfoCallback.cpp +++ b/lib/CGameInfoCallback.cpp @@ -479,6 +479,17 @@ std::vector CGameInfoCallback::getVisitableObjs(int3 return ret; } + +std::vector> CGameInfoCallback::getAllVisitableObjs() const +{ + std::vector> ret; + for(auto & obj : gs->map->objects) + if(obj->isVisitable() && obj->ID != Obj::EVENT && isVisible(obj)) + ret.push_back(obj); + + return ret; +} + const CGObjectInstance * CGameInfoCallback::getTopObj (int3 pos) const { return vstd::backOrNull(getVisitableObjs(pos)); diff --git a/lib/CGameInfoCallback.h b/lib/CGameInfoCallback.h index 91c51b5a7..25f15d53e 100644 --- a/lib/CGameInfoCallback.h +++ b/lib/CGameInfoCallback.h @@ -11,6 +11,7 @@ #include "int3.h" #include "ResourceSet.h" // for Res +#include "ConstTransitivePtr.h" #define ASSERT_IF_CALLED_WITH_PLAYER if(!getPlayerID()) {logGlobal->error(BOOST_CURRENT_FUNCTION); assert(0);} @@ -189,6 +190,7 @@ public: const CGObjectInstance * getObj(ObjectInstanceID objid, bool verbose = true) const override; virtual std::vector getBlockingObjs(int3 pos)const; std::vector getVisitableObjs(int3 pos, bool verbose = true) const override; + std::vector> getAllVisitableObjs() const; virtual std::vector getFlaggableObjects(int3 pos) const; virtual const CGObjectInstance * getTopObj (int3 pos) const; virtual PlayerColor getOwner(ObjectInstanceID heroID) const; diff --git a/lib/mapObjectConstructors/CObjectClassesHandler.cpp b/lib/mapObjectConstructors/CObjectClassesHandler.cpp index f8939be58..202372cf9 100644 --- a/lib/mapObjectConstructors/CObjectClassesHandler.cpp +++ b/lib/mapObjectConstructors/CObjectClassesHandler.cpp @@ -358,7 +358,7 @@ TObjectTypeHandler CObjectClassesHandler::getHandlerFor(MapObjectID type, MapObj return mapObjectTypes.front()->objectTypeHandlers.front(); auto subID = subtype.getNum(); - if (type == Obj::PRISON || type == Obj::HERO_PLACEHOLDER) + if (type == Obj::PRISON || type == Obj::HERO_PLACEHOLDER || type == Obj::SPELL_SCROLL) subID = 0; auto result = mapObjectTypes.at(type.getNum())->objectTypeHandlers.at(subID);