diff --git a/binaries/data/mods/public/gui/session_new/input.js b/binaries/data/mods/public/gui/session_new/input.js index ce566f9206..56a605768d 100644 --- a/binaries/data/mods/public/gui/session_new/input.js +++ b/binaries/data/mods/public/gui/session_new/input.js @@ -37,9 +37,18 @@ specialKeyStates[SDLK_RCTRL] = 0; specialKeyStates[SDLK_LCTRL] = 0; specialKeyStates[SDLK_RALT] = 0; specialKeyStates[SDLK_LALT] = 0; - // (TODO: maybe we should fix the hotkey system to be usable in this situation, -// rather than hardcoding Shift into this code?) +// rather than hardcoding Shift/Ctrl/Alt into this code?) + +// Number of pixels the mouse can move before the action is considered a drag +var maxDragDelta = 4; + +// Time in milliseconds in which a double click is recognized +const doubleClickTime = 500; +var doubleClickTimer = 0; +var doubleClicked = false; +// Store the previously clicked entity - ensure a double/triple click happens on the same entity +var prevClickedEntity = 0; function updateCursor() { @@ -125,7 +134,6 @@ function determineAction(x, y, fromMinimap) var playerOwned = ((targetState.player == entState.player)? true : false); var enemyOwned = ((targetState.player != entState.player)? true : false); var gaiaOwned = ((targetState.player == 0)? true : false); - if (targetState.garrisonHolder && playerOwned && ctrlPressed) { @@ -571,7 +579,7 @@ function handleInputAfterGui(ev) // If the mouse moved further than a limit, switch to bandbox mode var dragDeltaX = ev.x - dragStart[0]; var dragDeltaY = ev.y - dragStart[1]; - var maxDragDelta = 4; + if (Math.abs(dragDeltaX) >= maxDragDelta || Math.abs(dragDeltaY) >= maxDragDelta) { inputState = INPUT_BANDBOXING; @@ -593,12 +601,51 @@ function handleInputAfterGui(ev) return true; } + var onScreenOnly; + var selectedEntity = ents[0]; + var now = new Date(); + + if ((now.getTime() - doubleClickTimer < doubleClickTime) && (selectedEntity == prevClickedEntity)) + { + // Double click or triple click has occurred + + // Check for double click or triple click + if (!doubleClicked) + { + // If double click hasn't already occurred, this is a double click. + // Select only similar on-screen units + onScreenOnly = true; + doubleClicked = true; + } + else + { + // Double click has already occurred, so this is a triple click. + // Select all similar units whether they are on-screen or not + onScreenOnly = false; + } + + var templateToMatch = Engine.GuiInterfaceCall("GetEntityState", selectedEntity).template; + + ents = Engine.PickSimilarFriendlyEntities(templateToMatch, onScreenOnly); + } + else + { + // It's single click right now but it may become double or triple click + doubleClicked = false; + doubleClickTimer = now.getTime(); + prevClickedEntity = selectedEntity; + + // We only want to include the first picked unit in the selection + ents = [ents[0]]; + } + // If shift is pressed, don't reset the selection, but allow units to be added to the existing selection var addition = (specialKeyStates[SDLK_RSHIFT] || specialKeyStates[SDLK_LSHIFT]); if (!addition) - g_Selection.reset(); + g_Selection.reset(); - g_Selection.addList([ents[0]]); + g_Selection.addList(ents); + inputState = INPUT_NORMAL; return true; } diff --git a/source/gui/scripting/ScriptFunctions.cpp b/source/gui/scripting/ScriptFunctions.cpp index 5f559ed121..d625a36fa9 100644 --- a/source/gui/scripting/ScriptFunctions.cpp +++ b/source/gui/scripting/ScriptFunctions.cpp @@ -128,6 +128,11 @@ std::vector PickFriendlyEntitiesInRect(void* UNUSED(cbdata), int x0 return EntitySelection::PickEntitiesInRect(*g_Game->GetSimulation2(), *g_Game->GetView()->GetCamera(), x0, y0, x1, y1, player); } +std::vector PickSimilarFriendlyEntities(void* UNUSED(cbdata), std::string templateName, bool onScreenOnly) +{ + return EntitySelection::PickSimilarEntities(*g_Game->GetSimulation2(), *g_Game->GetView()->GetCamera(), templateName, g_Game->GetPlayerID(), onScreenOnly); +} + CFixedVector3D GetTerrainAtPoint(void* UNUSED(cbdata), int x, int y) { CVector3D pos = g_Game->GetView()->GetCamera()->GetWorldCoordinates(x, y, true); @@ -374,6 +379,7 @@ void GuiScriptingInit(ScriptInterface& scriptInterface) // Entity picking scriptInterface.RegisterFunction, int, int, &PickEntitiesAtPoint>("PickEntitiesAtPoint"); scriptInterface.RegisterFunction, int, int, int, int, int, &PickFriendlyEntitiesInRect>("PickFriendlyEntitiesInRect"); + scriptInterface.RegisterFunction, std::string, bool, &PickSimilarFriendlyEntities>("PickSimilarFriendlyEntities"); scriptInterface.RegisterFunction("GetTerrainAtPoint"); // Network / game setup functions diff --git a/source/simulation2/components/CCmpTemplateManager.cpp b/source/simulation2/components/CCmpTemplateManager.cpp index 87d7ce6dc1..22dab1e62b 100644 --- a/source/simulation2/components/CCmpTemplateManager.cpp +++ b/source/simulation2/components/CCmpTemplateManager.cpp @@ -131,6 +131,8 @@ public: virtual std::vector FindAllTemplates(); + virtual std::vector GetEntitiesUsingTemplate(std::string templateName); + private: // Entity template XML validator RelaxNGValidator m_Validator; @@ -405,6 +407,20 @@ std::vector CCmpTemplateManager::FindAllTemplates() return templates; } +/** + * Get the list of entities using the specified template + */ +std::vector CCmpTemplateManager::GetEntitiesUsingTemplate(std::string templateName) +{ + std::vector entities; + for (std::map::const_iterator it = m_LatestTemplates.begin(); it != m_LatestTemplates.end(); ++it) + { + if(it->second == templateName) + entities.push_back(it->first); + } + return entities; +} + void CCmpTemplateManager::CopyPreviewSubset(CParamNode& out, const CParamNode& in, bool corpse) { // We only want to include components which are necessary (for the visual previewing of an entity) diff --git a/source/simulation2/components/ICmpTemplateManager.cpp b/source/simulation2/components/ICmpTemplateManager.cpp index c8c46e853a..c7e23e08a9 100644 --- a/source/simulation2/components/ICmpTemplateManager.cpp +++ b/source/simulation2/components/ICmpTemplateManager.cpp @@ -25,4 +25,5 @@ BEGIN_INTERFACE_WRAPPER(TemplateManager) DEFINE_INTERFACE_METHOD_1("GetTemplate", const CParamNode*, ICmpTemplateManager, GetTemplate, std::string) DEFINE_INTERFACE_METHOD_1("GetCurrentTemplateName", std::string, ICmpTemplateManager, GetCurrentTemplateName, entity_id_t) DEFINE_INTERFACE_METHOD_0("FindAllTemplates", std::vector, ICmpTemplateManager, FindAllTemplates) +DEFINE_INTERFACE_METHOD_1("GetEntitiesUsingTemplate", std::vector, ICmpTemplateManager, GetEntitiesUsingTemplate, std::string) END_INTERFACE_WRAPPER(TemplateManager) diff --git a/source/simulation2/components/ICmpTemplateManager.h b/source/simulation2/components/ICmpTemplateManager.h index ef15a87abf..e5a7d99c9a 100644 --- a/source/simulation2/components/ICmpTemplateManager.h +++ b/source/simulation2/components/ICmpTemplateManager.h @@ -79,6 +79,11 @@ public: */ virtual std::string GetCurrentTemplateName(entity_id_t ent) = 0; + /** + * Returns the list of entities having the specified template. + */ + virtual std::vector GetEntitiesUsingTemplate(std::string templateName) = 0; + /** * Returns a list of strings that could be validly passed as @c templateName to LoadTemplate. * (This includes "actor|foo" etc names). diff --git a/source/simulation2/helpers/Selection.cpp b/source/simulation2/helpers/Selection.cpp index d424e69eeb..2d8d9b9838 100644 --- a/source/simulation2/helpers/Selection.cpp +++ b/source/simulation2/helpers/Selection.cpp @@ -23,6 +23,7 @@ #include "simulation2/Simulation2.h" #include "simulation2/components/ICmpOwnership.h" #include "simulation2/components/ICmpRangeManager.h" +#include "simulation2/components/ICmpTemplateManager.h" #include "simulation2/components/ICmpSelectable.h" #include "simulation2/components/ICmpVisual.h" @@ -130,3 +131,44 @@ std::vector EntitySelection::PickEntitiesInRect(CSimulation2& simul return hitEnts; } + +std::vector EntitySelection::PickSimilarEntities(CSimulation2& simulation, const CCamera& camera, std::string templateName, int owner, bool onScreenOnly) +{ + CmpPtr cmpTemplateManager(simulation, SYSTEM_ENTITY); + CmpPtr cmpRangeManager(simulation, SYSTEM_ENTITY); + + std::vector hitEnts; + + std::vector entities = cmpTemplateManager->GetEntitiesUsingTemplate(templateName); + for (std::vector::iterator it = entities.begin(); it != entities.end(); ++it) + { + entity_id_t ent = *it; + + // Ignore entities hidden by LOS (or otherwise hidden, e.g. when not IsInWorld) + // In this case, the checking is done to avoid selecting garrisoned units + if (cmpRangeManager->GetLosVisibility(ent, owner) == ICmpRangeManager::VIS_HIDDEN) + continue; + + // Ignore entities not owned by 'owner' + CmpPtr cmpOwnership(simulation.GetSimContext(), ent); + if (cmpOwnership.null() || cmpOwnership->GetOwner() != owner) + continue; + + if (onScreenOnly) + { + // Find the current interpolated model position. + CmpPtr cmpVisual(simulation.GetSimContext(), ent); + if (cmpVisual.null()) + continue; + CVector3D position = cmpVisual->GetPosition(); + + // Reject if it's not on-screen (e.g. it's behind the camera) + if (!camera.GetFrustum().IsPointVisible(position)) + continue; + } + + hitEnts.push_back(ent); + } + + return hitEnts; +} diff --git a/source/simulation2/helpers/Selection.h b/source/simulation2/helpers/Selection.h index c2021a67b2..ab91e2c866 100644 --- a/source/simulation2/helpers/Selection.h +++ b/source/simulation2/helpers/Selection.h @@ -47,6 +47,13 @@ std::vector PickEntitiesAtPoint(CSimulation2& simulation, const CCa */ std::vector PickEntitiesInRect(CSimulation2& simulation, const CCamera& camera, int sx0, int sy0, int sx1, int sy1, int owner); +/** + * Finds all entities with the given entity template name, that belong to player @p owner. + * If @p onScreenOnly then only entities visible on the screen will be selected, + * else all entities visible in the world will be selected. + */ +std::vector PickSimilarEntities(CSimulation2& simulation, const CCamera& camera, std::string templateName, int owner, bool onScreenOnly); + } // namespace #endif // INCLUDED_SELECTION