浏览代码

Merge pull request #4705 from Laserlicht/adv_search

map object search
Ivan Savenko 1 年之前
父节点
当前提交
abf9d1017e

+ 2 - 0
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!",

+ 2 - 0
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!",

+ 63 - 1
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<AdventureMapShortcutState> 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<ObjectInstanceID> 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<std::string, int> mapObjCount;
+	for(auto & obj : visitableObjInstances)
+		mapObjCount[{ LOCPLINT->cb->getObjInstance(obj)->getObjectName() }]++;
+
+	// convert to vector for indexed access
+	std::vector<std::pair<std::string, int>> 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<std::string> 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<ObjectInstanceID> 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<CObjectListWindow>(texts, nullptr, CGI->generaltexth->translate("vcmi.adventureMap.search.hover"), CGI->generaltexth->translate("vcmi.adventureMap.search.help"), [selectObjOnMap](int index){ selectObjOnMap(index); }, lastSel, std::vector<std::shared_ptr<IImage>>(), true);
+}
+
 void AdventureMapShortcuts::nextObject()
 {
 	const CGHeroInstance *h = LOCPLINT->localState->getCurrentHero();

+ 4 - 0
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:

+ 2 - 0
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,

+ 2 - 0
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 },

+ 3 - 0
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)

+ 49 - 14
client/windows/GUIClasses.cpp

@@ -1480,39 +1480,47 @@ void CObjectListWindow::CItem::showPopupWindow(const Point & cursorPosition)
 		parent->onPopup(index);
 }
 
-CObjectListWindow::CObjectListWindow(const std::vector<int> & _items, std::shared_ptr<CIntObject> titleWidget_, std::string _title, std::string _descr, std::function<void(int)> Callback, size_t initialSelection, std::vector<std::shared_ptr<IImage>> images)
+CObjectListWindow::CObjectListWindow(const std::vector<int> & _items, std::shared_ptr<CIntObject> titleWidget_, std::string _title, std::string _descr, std::function<void(int)> Callback, size_t initialSelection, std::vector<std::shared_ptr<IImage>> 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<std::string> & _items, std::shared_ptr<CIntObject> titleWidget_, std::string _title, std::string _descr, std::function<void(int)> Callback, size_t initialSelection, std::vector<std::shared_ptr<IImage>> images)
+CObjectListWindow::CObjectListWindow(const std::vector<std::string> & _items, std::shared_ptr<CIntObject> titleWidget_, std::string _title, std::string _descr, std::function<void(int)> Callback, size_t initialSelection, std::vector<std::shared_ptr<IImage>> 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<CIntObject> titleWidget_, std::string _title, std::string _descr)
+void CObjectListWindow::init(std::shared_ptr<CIntObject> titleWidget_, std::string _title, std::string _descr, bool searchBoxEnabled)
 {
 	titleWidget = titleWidget_;
 
@@ -1527,24 +1535,51 @@ void CObjectListWindow::init(std::shared_ptr<CIntObject> titleWidget_, std::stri
 		titleWidget->pos.y =75 + pos.y - titleWidget->pos.h/2;
 	}
 	list = std::make_shared<CListBox>(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<CButton>(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<TransparentFilledRectangle>(r.resize(1), rectangleColor, borderColor);
+	searchBoxDescription = std::make_shared<CLabel>(r.center().x, r.center().y, FONT_SMALL, ETextAlignment::CENTER, grayedColor, CGI->generaltexth->translate("vcmi.spellBook.search"));
+
+	searchBox = std::make_shared<CTextInput>(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<CIntObject> CObjectListWindow::genItem(size_t index)
 {
-	if(index < items.size())
-		return std::make_shared<CItem>(this, index, items[index].second);
+	if(index < itemsVisible.size())
+		return std::make_shared<CItem>(this, index, itemsVisible[index].second);
 	return std::shared_ptr<CIntObject>();
 }
 
 void CObjectListWindow::elementSelected()
 {
 	std::function<void(int)> 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<int>(items.size());
+		sel = static_cast<int>(itemsVisible.size());
 
 	break; default:
 		return;
 	}
 
-	vstd::abetween<int>(sel, 0, items.size()-1);
-	list->scrollTo(sel);
+	vstd::abetween<int>(sel, 0, itemsVisible.size()-1);
+	list->scrollTo(sel - 4); // -4 is for centering (list have 9 elements)
 	changeSelection(sel);
 }
 

+ 10 - 4
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<CButton> ok;
 	std::shared_ptr<CButton> exit;
 
-	std::vector< std::pair<int, std::string> > items;//all items present in list
+	std::shared_ptr<CTextInput> searchBox;
+	std::shared_ptr<TransparentFilledRectangle> searchBoxRectangle;
+	std::shared_ptr<CLabel> searchBoxDescription;
 
-	void init(std::shared_ptr<CIntObject> titleWidget_, std::string _title, std::string _descr);
+	std::vector< std::pair<int, std::string> > items; //all items present in list
+	std::vector< std::pair<int, std::string> > itemsVisible; //visible items present in list
+
+	void init(std::shared_ptr<CIntObject> 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<int> &_items, std::shared_ptr<CIntObject> titleWidget_, std::string _title, std::string _descr, std::function<void(int)> Callback, size_t initialSelection = 0, std::vector<std::shared_ptr<IImage>> images = {});
-	CObjectListWindow(const std::vector<std::string> &_items, std::shared_ptr<CIntObject> titleWidget_, std::string _title, std::string _descr, std::function<void(int)> Callback, size_t initialSelection = 0, std::vector<std::shared_ptr<IImage>> images = {});
+	CObjectListWindow(const std::vector<int> &_items, std::shared_ptr<CIntObject> titleWidget_, std::string _title, std::string _descr, std::function<void(int)> Callback, size_t initialSelection = 0, std::vector<std::shared_ptr<IImage>> images = {}, bool searchBoxEnabled = false);
+	CObjectListWindow(const std::vector<std::string> &_items, std::shared_ptr<CIntObject> titleWidget_, std::string _title, std::string _descr, std::function<void(int)> Callback, size_t initialSelection = 0, std::vector<std::shared_ptr<IImage>> images = {}, bool searchBoxEnabled = false);
 
 	std::shared_ptr<CIntObject> genItem(size_t index);
 	void elementSelected();//call callback and close this window

+ 2 - 0
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",

+ 11 - 0
lib/CGameInfoCallback.cpp

@@ -479,6 +479,17 @@ std::vector <const CGObjectInstance *> CGameInfoCallback::getVisitableObjs(int3
 
 	return ret;
 }
+
+std::vector<ConstTransitivePtr<CGObjectInstance>> CGameInfoCallback::getAllVisitableObjs() const
+{
+	std::vector<ConstTransitivePtr<CGObjectInstance>> 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));

+ 2 - 0
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 <const CGObjectInstance * > getBlockingObjs(int3 pos)const;
 	std::vector <const CGObjectInstance * > getVisitableObjs(int3 pos, bool verbose = true) const override;
+	std::vector<ConstTransitivePtr<CGObjectInstance>> getAllVisitableObjs() const;
 	virtual std::vector <const CGObjectInstance * > getFlaggableObjects(int3 pos) const;
 	virtual const CGObjectInstance * getTopObj (int3 pos) const;
 	virtual PlayerColor getOwner(ObjectInstanceID heroID) const;

+ 1 - 1
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);