diff --git a/binaries/data/config/default.cfg b/binaries/data/config/default.cfg index 065e5134c9..f9db3f2fd5 100644 --- a/binaries/data/config/default.cfg +++ b/binaries/data/config/default.cfg @@ -134,6 +134,7 @@ hotkey.console.paste = "Ctrl+V" ; Paste clipboard to console hotkey.selection.add = Shift ; Add units to selection hotkey.selection.remove = Ctrl ; Remove units from selection hotkey.selection.idle = Period ; Select next idle unit +hotkey.selection.offscreen = Alt ; Include offscreen units in selection hotkey.selection.group.select.0 = 0 hotkey.selection.group.save.0 = "Ctrl+0" hotkey.selection.group.add.0 = "Shift+0" diff --git a/binaries/data/mods/public/gui/session/input.js b/binaries/data/mods/public/gui/session/input.js index 783a9304c3..05f3ecd2fd 100644 --- a/binaries/data/mods/public/gui/session/input.js +++ b/binaries/data/mods/public/gui/session/input.js @@ -726,7 +726,9 @@ function handleInputAfterGui(ev) return true; } - var onScreenOnly; + var showOffscreen = Engine.HotkeyIsPressed("selection.offscreen"); + var matchRank; + var templateToMatch; var selectedEntity = ents[0]; var now = new Date(); @@ -738,20 +740,20 @@ function handleInputAfterGui(ev) if (!doubleClicked) { // If double click hasn't already occurred, this is a double click. - // Select only similar on-screen units - onScreenOnly = true; + // Select units matching exact rank + templateToMatch = Engine.GuiInterfaceCall("GetEntityState", selectedEntity).template; + matchRank = 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; + // Select all similar units regardless of rank + templateToMatch = Engine.GuiInterfaceCall("GetEntityState", selectedEntity).identity.selectionGroupName; + matchRank = false; } - var templateToMatch = Engine.GuiInterfaceCall("GetEntityState", selectedEntity).template; - - ents = Engine.PickSimilarFriendlyEntities(templateToMatch, onScreenOnly); + ents = Engine.PickSimilarFriendlyEntities(templateToMatch, showOffscreen, matchRank); } else { diff --git a/binaries/data/mods/public/simulation/components/GuiInterface.js b/binaries/data/mods/public/simulation/components/GuiInterface.js index 6f5a1cd873..7b61bc491c 100644 --- a/binaries/data/mods/public/simulation/components/GuiInterface.js +++ b/binaries/data/mods/public/simulation/components/GuiInterface.js @@ -119,7 +119,8 @@ GuiInterface.prototype.GetEntityState = function(player, ent) { ret.identity = { "rank": cmpIdentity.GetRank(), - "classes": cmpIdentity.GetClassesList() + "classes": cmpIdentity.GetClassesList(), + "selectionGroupName": cmpIdentity.GetSelectionGroupName() }; } diff --git a/binaries/data/mods/public/simulation/components/Identity.js b/binaries/data/mods/public/simulation/components/Identity.js index 2984c37bbf..179acb7f0e 100644 --- a/binaries/data/mods/public/simulation/components/Identity.js +++ b/binaries/data/mods/public/simulation/components/Identity.js @@ -139,4 +139,9 @@ Identity.prototype.HasClass = function(name) return this.GetClassesList().indexOf(name) != -1; }; +Identity.prototype.GetSelectionGroupName = function() +{ + return (this.template.SelectionGroupName || ""); +} + Engine.RegisterComponentType(IID_Identity, "Identity", Identity); diff --git a/binaries/data/mods/public/simulation/components/interfaces/Identity.js b/binaries/data/mods/public/simulation/components/interfaces/Identity.js deleted file mode 100644 index df761b0872..0000000000 --- a/binaries/data/mods/public/simulation/components/interfaces/Identity.js +++ /dev/null @@ -1 +0,0 @@ -Engine.RegisterInterface("Identity"); diff --git a/binaries/data/mods/public/simulation/templates/units/iber_cavalry_spearman_b.xml b/binaries/data/mods/public/simulation/templates/units/iber_cavalry_spearman_b.xml index 091f824935..ef90299b10 100644 --- a/binaries/data/mods/public/simulation/templates/units/iber_cavalry_spearman_b.xml +++ b/binaries/data/mods/public/simulation/templates/units/iber_cavalry_spearman_b.xml @@ -2,6 +2,7 @@ iber + units/iber_cavalry_spearman_b Epones units/iber_cavalry_spearman.png Armed like the light infantry, Iberian cavalry were often pursued as mercenaries, especially by the Carthaginians. Mounted on excellent horses and wielding high-grade swords they were capable of taking on heavy or light cavalry. As with all Iberians armor was scarce, but they wore the ubiquitous sinew caps made famous by the peoples of the peninsula. diff --git a/binaries/data/mods/public/simulation/templates/units/iber_infantry_javelinist_b.xml b/binaries/data/mods/public/simulation/templates/units/iber_infantry_javelinist_b.xml index 8ffe9631bd..61f81ec9b8 100644 --- a/binaries/data/mods/public/simulation/templates/units/iber_infantry_javelinist_b.xml +++ b/binaries/data/mods/public/simulation/templates/units/iber_infantry_javelinist_b.xml @@ -2,6 +2,7 @@ iber + units/iber_infantry_javelinist_b Caetrati Lusitano Iberians, especially the Lusitanians, were good at ranged combat and ambushing enemy columns. They throw heavy iron javelins and sometimes even add burning pitch to them, making them good as a cheap siege weapon. units/cart_cavalry_javelinist.png diff --git a/binaries/data/mods/public/simulation/templates/units/iber_infantry_slinger_b.xml b/binaries/data/mods/public/simulation/templates/units/iber_infantry_slinger_b.xml index 49c4a5e65f..0a9e69c918 100644 --- a/binaries/data/mods/public/simulation/templates/units/iber_infantry_slinger_b.xml +++ b/binaries/data/mods/public/simulation/templates/units/iber_infantry_slinger_b.xml @@ -2,6 +2,7 @@ iber + units/iber_infantry_slinger_b Karsken units/iber_infantry_slinger.png Iberian slingers were the undisputed masters of the weapon and extracted a high toll of the enemy. Going into combat scantily clad at best, the slinger carried three slings tied around his waist, each of a different length allowing him to attack opponents from all ranges. Unlike other cultures, the Iberian slingers threw rocks instead of specially made lead shot. diff --git a/binaries/data/mods/public/simulation/templates/units/iber_infantry_spearman_b.xml b/binaries/data/mods/public/simulation/templates/units/iber_infantry_spearman_b.xml index 04ce1a8c17..e80d988c70 100644 --- a/binaries/data/mods/public/simulation/templates/units/iber_infantry_spearman_b.xml +++ b/binaries/data/mods/public/simulation/templates/units/iber_infantry_spearman_b.xml @@ -2,6 +2,7 @@ iber + units/iber_infantry_spearman_b Scutari units/iber_infantry_spearman.png A long-bladed spear was a chief melee weapon of the Iberian infantry, often used after the javelins had been thrown. Typically carried by infantry known as scutarii for their long oval body shields, the spearmen would close in formation to attack their opponents. Usually lightly armored, they were quick and had a ferocious reputation. diff --git a/binaries/data/mods/public/simulation/templates/units/iber_infantry_swordsman_b.xml b/binaries/data/mods/public/simulation/templates/units/iber_infantry_swordsman_b.xml index d36cc3223d..d2a2d8bdf9 100644 --- a/binaries/data/mods/public/simulation/templates/units/iber_infantry_swordsman_b.xml +++ b/binaries/data/mods/public/simulation/templates/units/iber_infantry_swordsman_b.xml @@ -2,6 +2,7 @@ iber + units/iber_infantry_swordsman_b Caetrati units/iber_infantry_swordsman.png The Iberians were master sword-smiths and the falcata was their greatest creation. Wielded by superb swordsmen equipped with light armor and a buckler known as a caetra, they caused untold carnage. Thanks to this Iberian infantry were fast and agile unlike many of their opponents and could bite hard when they attacked. Their skill with sword and buckler were legendary, allowing them to go toe-to-toe with heavy infantry. diff --git a/source/gui/scripting/ScriptFunctions.cpp b/source/gui/scripting/ScriptFunctions.cpp index 4a3d3b97e5..4b8b211f00 100644 --- a/source/gui/scripting/ScriptFunctions.cpp +++ b/source/gui/scripting/ScriptFunctions.cpp @@ -133,9 +133,9 @@ 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) +std::vector PickSimilarFriendlyEntities(void* UNUSED(cbdata), std::string templateName, bool includeOffScreen, bool matchRank) { - return EntitySelection::PickSimilarEntities(*g_Game->GetSimulation2(), *g_Game->GetView()->GetCamera(), templateName, g_Game->GetPlayerID(), onScreenOnly); + return EntitySelection::PickSimilarEntities(*g_Game->GetSimulation2(), *g_Game->GetView()->GetCamera(), templateName, g_Game->GetPlayerID(), includeOffScreen, matchRank); } CFixedVector3D GetTerrainAtPoint(void* UNUSED(cbdata), int x, int y) @@ -465,7 +465,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, std::string, bool, bool, &PickSimilarFriendlyEntities>("PickSimilarFriendlyEntities"); scriptInterface.RegisterFunction("GetTerrainAtPoint"); // Network / game setup functions diff --git a/source/simulation2/TypeList.h b/source/simulation2/TypeList.h index 585c52c437..ea3b3c045c 100644 --- a/source/simulation2/TypeList.h +++ b/source/simulation2/TypeList.h @@ -76,6 +76,9 @@ COMPONENT(Footprint) INTERFACE(GuiInterface) COMPONENT(GuiInterfaceScripted) +INTERFACE(Identity) +COMPONENT(IdentityScripted) + INTERFACE(Minimap) COMPONENT(Minimap) diff --git a/source/simulation2/helpers/Selection.cpp b/source/simulation2/helpers/Selection.cpp index 42c82f8580..7de3caf5f6 100644 --- a/source/simulation2/helpers/Selection.cpp +++ b/source/simulation2/helpers/Selection.cpp @@ -21,6 +21,7 @@ #include "graphics/Camera.h" #include "simulation2/Simulation2.h" +#include "simulation2/components/ICmpIdentity.h" #include "simulation2/components/ICmpOwnership.h" #include "simulation2/components/ICmpRangeManager.h" #include "simulation2/components/ICmpTemplateManager.h" @@ -132,17 +133,38 @@ std::vector EntitySelection::PickEntitiesInRect(CSimulation2& simul return hitEnts; } -std::vector EntitySelection::PickSimilarEntities(CSimulation2& simulation, const CCamera& camera, const std::string& templateName, int owner, bool onScreenOnly) +std::vector EntitySelection::PickSimilarEntities(CSimulation2& simulation, const CCamera& camera, const std::string& templateName, int owner, bool includeOffScreen, bool matchRank) { 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; + const CSimulation2::InterfaceListUnordered& ents = simulation.GetEntitiesWithInterfaceUnordered(IID_Selectable); + for (CSimulation2::InterfaceListUnordered::const_iterator it = ents.begin(); it != ents.end(); ++it) + { + entity_id_t ent = it->first; + + CmpPtr cmpIdentity(simulation.GetSimContext(), ent); + + std::string groupName; + if (!cmpIdentity.null()) + { + groupName = cmpIdentity->GetSelectionGroupName(); + } + + if (!matchRank && !groupName.empty()) + { + // There's a selection group so match that + if (groupName.compare(templateName) != 0) + continue; + } + else + { + // Fall back to exact template name matching + if (cmpTemplateManager->GetCurrentTemplateName(ent).compare(templateName) != 0) + continue; + } // 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 @@ -154,7 +176,8 @@ std::vector EntitySelection::PickSimilarEntities(CSimulation2& simu if (cmpOwnership.null() || cmpOwnership->GetOwner() != owner) continue; - if (onScreenOnly) + // Ignore off screen entities + if (!includeOffScreen) { // Find the current interpolated model position. CmpPtr cmpVisual(simulation.GetSimContext(), ent); diff --git a/source/simulation2/helpers/Selection.h b/source/simulation2/helpers/Selection.h index db1342d9f3..16688bf14c 100644 --- a/source/simulation2/helpers/Selection.h +++ b/source/simulation2/helpers/Selection.h @@ -49,10 +49,12 @@ std::vector PickEntitiesInRect(CSimulation2& simulation, const CCam /** * 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. + * If @p includeOffScreen then all entities visible in the world will be selected, + * else only entities visible on the screen will be selected. + * If @p matchRank then only entities that exactly match @p templateName will be selected, + * else entities with matching SelectionGroupName will be selected. */ -std::vector PickSimilarEntities(CSimulation2& simulation, const CCamera& camera, const std::string& templateName, int owner, bool onScreenOnly); +std::vector PickSimilarEntities(CSimulation2& simulation, const CCamera& camera, const std::string& templateName, int owner, bool includeOffScreen, bool matchRank); } // namespace