diff --git a/code/ryzom/client/data/gamedev/interfaces_v3/commands.xml b/code/ryzom/client/data/gamedev/interfaces_v3/commands.xml
index 13356083c..e236e43c8 100644
--- a/code/ryzom/client/data/gamedev/interfaces_v3/commands.xml
+++ b/code/ryzom/client/data/gamedev/interfaces_v3/commands.xml
@@ -78,17 +78,11 @@
-
-
+
+
-
-
-
-
-
-
-
-
+
+
diff --git a/code/ryzom/client/src/entities.cpp b/code/ryzom/client/src/entities.cpp
index 7d16e51dc..495471da1 100644
--- a/code/ryzom/client/src/entities.cpp
+++ b/code/ryzom/client/src/entities.cpp
@@ -2334,7 +2334,63 @@ CEntityCL *CEntityManager::getEntityByName (uint32 stringId) const
}
//-----------------------------------------------
+CEntityCL *CEntityManager::getEntityByKeywords (const std::vector &keywords, bool onlySelectable) const
+{
+ if (keywords.empty()) return NULL;
+ std::vector lcKeywords;
+ lcKeywords.resize(keywords.size());
+ for(uint k = 0; k < keywords.size(); k++)
+ {
+ lcKeywords[k] = toLower(keywords[k]);
+ }
+
+ const NLMISC::CVectorD &userPosD = UserEntity->pos();
+ const uint count = (uint)_Entities.size();
+ uint selectedEntityId = 0;
+ float selectedEntityDist = FLT_MAX;
+ for(uint i = 0; i < count; ++i)
+ {
+ if (!_Entities[i]) continue;
+
+ if (onlySelectable && !_Entities[i]->properties().selectable()) continue;
+
+ ucstring lcName;
+ lcName = toLower(_Entities[i]->getDisplayName());
+ if (lcName.empty()) continue;
+
+ bool match = true;
+ for (uint k = 0; k < lcKeywords.size(); ++k)
+ {
+ if (lcName.find(lcKeywords[k]) == ucstring::npos)
+ {
+ match = false;
+ break;
+ }
+ }
+
+ if (match)
+ {
+ const NLMISC::CVectorD &targetPosD = _Entities[i]->pos();
+
+ float deltaX = (float) targetPosD.x - (float) userPosD.x;
+ float deltaY = (float) targetPosD.y - (float) userPosD.y;
+ float dist = (float)sqrt(deltaX * deltaX + deltaY * deltaY);
+ if (dist < selectedEntityDist)
+ {
+ selectedEntityDist = dist;
+ selectedEntityId = i;
+ }
+ }
+ }
+
+ if (selectedEntityDist != FLT_MAX)
+ return _Entities[selectedEntityId];
+ else
+ return NULL;
+}
+
+//-----------------------------------------------
CEntityCL *CEntityManager::getEntityByName (const ucstring &name, bool caseSensitive, bool complete) const
{
ucstring source = name;
diff --git a/code/ryzom/client/src/entities.h b/code/ryzom/client/src/entities.h
index fb8504fc1..1151a367d 100644
--- a/code/ryzom/client/src/entities.h
+++ b/code/ryzom/client/src/entities.h
@@ -302,6 +302,12 @@ public:
* \param complete : if true, the name must match the full name of the entity.
*/
CEntityCL *getEntityByName (const ucstring &name, bool caseSensitive, bool complete) const;
+ /**
+ * Case insensitive match against entity name. All listed keywords must match.
+ * \param keywords to match
+ * \param onlySelectable : if true, match only entity that can be selected
+ */
+ CEntityCL *getEntityByKeywords (const std::vector &keywords, bool onlySelectable) const;
CEntityCL *getEntityBySheetName (const std::string &sheet) const;
/// Get an entity by dataset index. Returns NULL if the entity is not found.
CEntityCL *getEntityByCompressedIndex(TDataSetIndex compressedIndex) const;
diff --git a/code/ryzom/client/src/interface_v3/action_handler_game.cpp b/code/ryzom/client/src/interface_v3/action_handler_game.cpp
index c5faa16d9..2f91cb9a5 100644
--- a/code/ryzom/client/src/interface_v3/action_handler_game.cpp
+++ b/code/ryzom/client/src/interface_v3/action_handler_game.cpp
@@ -2416,70 +2416,66 @@ class CAHTarget : public IActionHandler
{
virtual void execute (CCtrlBase * /* pCaller */, const string &Params)
{
- // Get the entity name to target
ucstring entityName;
- entityName.fromUtf8 (getParam (Params, "entity"));
- bool preferCompleteMatch = (getParam (Params, "prefer_complete_match") != "0");
+ entityName.fromUtf8(getParam(Params, "entity"));
+ if (entityName.empty()) return;
+
+ string completeMatch = getParam(Params, "prefer_complete_match");
bool quiet = (getParam (Params, "quiet") == "true");
- if (!entityName.empty())
+ vector keywords;
+ NLMISC::splitUCString(entityName, ucstring(" "), keywords);
+ if (!keywords.empty() && keywords[0].size() > 0 && keywords[0][0] == (ucchar)'"')
{
- CEntityCL *entity = NULL;
- if (preferCompleteMatch)
- {
- // Try to get the entity with complete match first
- entity = EntitiesMngr.getEntityByName (entityName, false, true);
- }
-
- if (entity == NULL)
- {
- // Get the entity with a partial match
- entity = EntitiesMngr.getEntityByName (entityName, false, false);
- }
+ // entity name is in quotes, do old style match with 'starts with' filter
+ // search for optional second parameter from old command for prefer_complete_match param
+ keywords.clear();
- if (entity == NULL)
- {
- //Get the entity with a sheetName
- entity = EntitiesMngr.getEntityBySheetName(entityName.toUtf8());
- }
-
- if (entity)
- {
- CCharacterCL *character = dynamic_cast(entity);
- if (character != NULL)
- {
- if(character->isSelectableBySpace())
- {
- nldebug("isSelectableBySpace");
- }
- else
- {
- nldebug("is not isSelectableBySpace");
- }
- }
- if(entity->properties().selectable())
- {
- nldebug("is prop selectable");
- }
- else
- {
- // to avoid campfire selection exploit #316
- nldebug("is not prop selectable");
- CInterfaceManager *pIM= CInterfaceManager::getInstance();
- if(!quiet)
- pIM->displaySystemInfo(CI18N::get("uiTargetErrorCmd"));
- return;
- }
+ ucstring::size_type lastOf = entityName.rfind(ucstring("\""));
+ if (lastOf == 0)
+ lastOf = ucstring::npos;
- // Select the entity
- UserEntity->selection(entity->slot());
- }
- else
- {
- CInterfaceManager *pIM= CInterfaceManager::getInstance();
- if(!quiet)
- pIM->displaySystemInfo(CI18N::get("uiTargetErrorCmd"));
- }
+ // override the value only when there is no 'prefer_complete_match' parameter set
+ if (completeMatch.empty() && lastOf < entityName.size())
+ completeMatch = trim(entityName.substr(lastOf+1).toUtf8());
+
+ entityName = entityName.substr(1, lastOf-1);
+ }
+
+ // late check because only possible if doing 'starts-with' search
+ bool preferCompleteMatch = (completeMatch != "0");
+
+ CEntityCL *entity = NULL;
+ if (preferCompleteMatch)
+ {
+ // Try to get the entity with complete match first
+ entity = EntitiesMngr.getEntityByName (entityName, false, true);
+ }
+
+ if (entity == NULL && !keywords.empty())
+ {
+ entity = EntitiesMngr.getEntityByKeywords(keywords, true);
+ }
+
+ if (entity == NULL)
+ {
+ // Get the entity with a partial match using 'starts with' search
+ entity = EntitiesMngr.getEntityByName(entityName, false, false);
+ }
+
+ if (entity == NULL)
+ {
+ //Get the entity with a sheetName
+ entity = EntitiesMngr.getEntityBySheetName(entityName.toUtf8());
+ }
+
+ if (entity && entity->properties().selectable() && !entity->getDisplayName().empty())
+ {
+ UserEntity->selection(entity->slot());
+ }
+ else if (!quiet)
+ {
+ CInterfaceManager::getInstance()->displaySystemInfo(CI18N::get("uiTargetErrorCmd"));
}
}
};