diff --git a/binaries/data/mods/public/gui/session_new/input.js b/binaries/data/mods/public/gui/session_new/input.js
index d48f4fe0ac..7613f717f6 100644
--- a/binaries/data/mods/public/gui/session_new/input.js
+++ b/binaries/data/mods/public/gui/session_new/input.js
@@ -6,8 +6,9 @@ const SDL_BUTTON_RIGHT = 3;
var INPUT_NORMAL = 0;
-var INPUT_DRAGGING = 1;
-var INPUT_BUILDING_PLACEMENT = 2;
+var INPUT_SELECTING = 1;
+var INPUT_BANDBOXING = 2;
+var INPUT_BUILDING_PLACEMENT = 3;
var inputState = INPUT_NORMAL;
@@ -46,7 +47,7 @@ function findGatherType(gatherer, supply)
*/
function determineAction(x, y)
{
- var selection = getEntitySelection();
+ var selection = g_Selection.toList();
// No action if there's no selection
if (!selection.length)
@@ -118,6 +119,10 @@ Selection methods: (not all currently implemented)
*/
+// TODO: it'd probably be nice to have a better state-machine system
+
+var selectionDragStart;
+
function handleInputAfterGui(ev)
{
switch (inputState)
@@ -125,19 +130,16 @@ function handleInputAfterGui(ev)
case INPUT_NORMAL:
switch (ev.type)
{
+ case "mousemotion":
+ var ents = Engine.PickEntitiesAtPoint(ev.x, ev.y);
+ g_Selection.setHighlightList(ents);
+ return false;
+
case "mousebuttondown":
if (ev.button == SDL_BUTTON_LEFT)
{
- var ents = Engine.PickEntitiesAtPoint(ev.x, ev.y);
- if (!ents.length)
- {
- resetEntitySelection();
- return true;
- }
-
- resetEntitySelection();
- addEntitySelection([ents[0]]);
-
+ selectionDragStart = [ ev.x, ev.y ];
+ inputState = INPUT_SELECTING;
return true;
}
else if (ev.button == SDL_BUTTON_RIGHT)
@@ -146,7 +148,7 @@ function handleInputAfterGui(ev)
if (!action)
break;
- var selection = getEntitySelection();
+ var selection = g_Selection.toList();
switch (action.type)
{
@@ -164,6 +166,92 @@ function handleInputAfterGui(ev)
return true;
}
}
+ break;
+ }
+ break;
+
+ case INPUT_SELECTING:
+ switch (ev.type)
+ {
+ case "mousemotion":
+ // If the mouse moved further than a limit, switch to bandbox mode
+ var dragDeltaX = ev.x - selectionDragStart[0];
+ var dragDeltaY = ev.y - selectionDragStart[1];
+ var maxDragDelta = 4;
+ if (Math.abs(dragDeltaX) >= maxDragDelta || Math.abs(dragDeltaY) >= maxDragDelta)
+ {
+ inputState = INPUT_BANDBOXING;
+ return false;
+ }
+
+ var ents = Engine.PickEntitiesAtPoint(ev.x, ev.y);
+ g_Selection.setHighlightList(ents);
+ return false;
+
+ case "mousebuttonup":
+ if (ev.button == SDL_BUTTON_LEFT)
+ {
+ var ents = Engine.PickEntitiesAtPoint(ev.x, ev.y);
+ if (!ents.length)
+ {
+ g_Selection.reset();
+
+ inputState = INPUT_NORMAL;
+ return true;
+ }
+
+ g_Selection.reset();
+ g_Selection.addList([ents[0]]);
+
+ inputState = INPUT_NORMAL;
+ return true;
+ }
+ break;
+ }
+ break;
+
+ case INPUT_BANDBOXING:
+ switch (ev.type)
+ {
+ case "mousemotion":
+ var x0 = selectionDragStart[0];
+ var y0 = selectionDragStart[1];
+ var x1 = ev.x;
+ var y1 = ev.y;
+ if (x0 > x1) { var t = x0; x0 = x1; x1 = t; }
+ if (y0 > y1) { var t = y0; y0 = y1; y1 = t; }
+
+ var bandbox = getGUIObjectByName("bandbox");
+ bandbox.size = [x0, y0, x1, y1].join(" ");
+ bandbox.hidden = false;
+
+ var ents = Engine.PickFriendlyEntitiesInRect(x0, y0, x1, y1, Engine.GetPlayerID());
+ g_Selection.setHighlightList(ents);
+
+ return false;
+
+ case "mousebuttonup":
+ if (ev.button == SDL_BUTTON_LEFT)
+ {
+ var x0 = selectionDragStart[0];
+ var y0 = selectionDragStart[1];
+ var x1 = ev.x;
+ var y1 = ev.y;
+ if (x0 > x1) { var t = x0; x0 = x1; x1 = t; }
+ if (y0 > y1) { var t = y0; y0 = y1; y1 = t; }
+
+ var bandbox = getGUIObjectByName("bandbox");
+ bandbox.hidden = true;
+
+ var ents = Engine.PickFriendlyEntitiesInRect(x0, y0, x1, y1, Engine.GetPlayerID());
+ g_Selection.setHighlightList([]);
+ g_Selection.reset();
+ g_Selection.addList(ents);
+
+ inputState = INPUT_NORMAL;
+ return true;
+ }
+ break;
}
break;
@@ -195,6 +283,7 @@ function handleInputAfterGui(ev)
inputState = INPUT_NORMAL;
return true;
}
+ break;
}
break;
}
diff --git a/binaries/data/mods/public/gui/session_new/selection.js b/binaries/data/mods/public/gui/session_new/selection.js
index 13d72c4d9e..bfecbc32b2 100644
--- a/binaries/data/mods/public/gui/session_new/selection.js
+++ b/binaries/data/mods/public/gui/session_new/selection.js
@@ -1,51 +1,88 @@
-var g_Selection = {}; // { id: 1, id: 1, ... } for each selected entity ID 'id'
+var g_ActiveSelectionColour = { r:1, g:1, b:1, a:1 };
+var g_HighlightSelectionColour = { r:1, g:1, b:1, a:0.5 };
+var g_InactiveSelectionColour = { r:1, g:1, b:1, a:0 };
-var g_ActiveSelectionColour = { r:1, g:1, b:1, a:1 };
-var g_InactiveSelectionColour = { r:0, g:0, b:0, a:0 };
-
-function setHighlight(ent, colour)
+function _setHighlight(ents, colour)
{
- Engine.GuiInterfaceCall("SetSelectionHighlight", { "entity":ent, "colour":colour });
+ Engine.GuiInterfaceCall("SetSelectionHighlight", { "entities":ents, "colour":colour });
}
-function toggleEntitySelection(ent)
+function EntitySelection()
{
- if (g_Selection[ent])
+ this.selected = {}; // { id: 1, id: 1, ... } for each selected entity ID 'id'
+ this.highlighted = {}; // { id: 1, ... } for mouseover-highlighted entity IDs
+}
+
+EntitySelection.prototype.toggle = function(ent)
+{
+ if (this.selected[ent])
{
- setHighlight(ent, g_InactiveSelectionColour);
- delete g_Selection[ent];
+ _setHighlight([ent], g_InactiveSelectionColour);
+ delete this.selected[ent];
}
else
{
- setHighlight(ent, g_ActiveSelectionColour);
- g_Selection[ent] = 1;
+ _setHighlight([ent], g_ActiveSelectionColour);
+ this.selected[ent] = 1;
}
-}
+};
-function addEntitySelection(ents)
+EntitySelection.prototype.addList = function(ents)
{
+ var added = [];
for each (var ent in ents)
{
- if (!g_Selection[ent])
+ if (!this.selected[ent])
{
- setHighlight(ent, g_ActiveSelectionColour);
- g_Selection[ent] = 1;
+ added.push(ent);
+ this.selected[ent] = 1;
}
}
-}
+ _setHighlight(added, g_ActiveSelectionColour);
+};
-function resetEntitySelection()
+EntitySelection.prototype.reset = function()
{
- for (var ent in g_Selection)
- setHighlight(ent, g_InactiveSelectionColour);
+ _setHighlight(this.toList(), g_InactiveSelectionColour);
+ this.selected = {};
+};
- g_Selection = {};
-}
-
-function getEntitySelection()
+EntitySelection.prototype.toList = function()
{
var ents = [];
- for (var ent in g_Selection)
+ for (var ent in this.selected)
ents.push(+ent); // convert from string to number and push
return ents;
-}
+};
+
+EntitySelection.prototype.setHighlightList = function(ents)
+{
+ var removed = [];
+ var added = [];
+
+ // Remove highlighting for the old units (excluding ones that are actively selected too)
+ for (var ent in this.highlighted)
+ if (!this.selected[ent])
+ removed.push(ent);
+
+ // Add new highlighting
+ for each (var ent in ents)
+ if (!this.selected[ent])
+ added.push(ent);
+
+ _setHighlight(removed, g_InactiveSelectionColour);
+ _setHighlight(added, g_HighlightSelectionColour);
+
+ // TODO: this could be a bit more efficient by only changing the ones that
+ // have entered/left the highlight list
+
+ // Store the new list
+ this.highlighted = {};
+ for each (var ent in ents)
+ this.highlighted[ent] = 1;
+};
+
+
+var g_Selection = new EntitySelection();
+
+
diff --git a/binaries/data/mods/public/gui/session_new/session.js b/binaries/data/mods/public/gui/session_new/session.js
index 9aa388f6c7..b146883386 100644
--- a/binaries/data/mods/public/gui/session_new/session.js
+++ b/binaries/data/mods/public/gui/session_new/session.js
@@ -6,7 +6,7 @@ function init(initData, hotloadData)
{
if (hotloadData)
{
- g_Selection = hotloadData.selection;
+ g_Selection.selected = hotloadData.selection;
}
onSimulationUpdate();
@@ -15,7 +15,7 @@ function init(initData, hotloadData)
// Return some data that we'll use when hotloading this file after changes
function getHotloadData()
{
- return { selection: g_Selection };
+ return { selection: g_Selection.selected };
}
function onTick()
@@ -48,7 +48,7 @@ function updateDebug(simState)
var debug = getGUIObjectByName("debug");
var text = uneval(simState);
- var selection = getEntitySelection();
+ var selection = g_Selection.toList();
if (selection.length)
{
var entState = Engine.GuiInterfaceCall("GetEntityState", selection[0]);
@@ -88,7 +88,7 @@ function updateUnitDisplay()
var detailsPanel = getGUIObjectByName("selectionDetails");
var commandsPanel = getGUIObjectByName("unitCommands");
- var selection = getEntitySelection();
+ var selection = g_Selection.toList();
if (selection.length == 0)
{
detailsPanel.hidden = true;
diff --git a/binaries/data/mods/public/gui/session_new/session.xml b/binaries/data/mods/public/gui/session_new/session.xml
index e715aabd99..4a2b698b79 100644
--- a/binaries/data/mods/public/gui/session_new/session.xml
+++ b/binaries/data/mods/public/gui/session_new/session.xml
@@ -230,4 +230,7 @@
+
+
+
diff --git a/binaries/data/mods/public/gui/session_new/sprites.xml b/binaries/data/mods/public/gui/session_new/sprites.xml
index 406077bcd8..7e1f49fb40 100644
--- a/binaries/data/mods/public/gui/session_new/sprites.xml
+++ b/binaries/data/mods/public/gui/session_new/sprites.xml
@@ -87,5 +87,16 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/binaries/data/mods/public/simulation/components/GuiInterface.js b/binaries/data/mods/public/simulation/components/GuiInterface.js
index 3e46414559..87d992113a 100644
--- a/binaries/data/mods/public/simulation/components/GuiInterface.js
+++ b/binaries/data/mods/public/simulation/components/GuiInterface.js
@@ -119,9 +119,12 @@ GuiInterface.prototype.GetTemplateData = function(player, name)
GuiInterface.prototype.SetSelectionHighlight = function(player, cmd)
{
- var cmpSelectable = Engine.QueryInterface(cmd.entity, IID_Selectable);
- if (cmpSelectable)
- cmpSelectable.SetSelectionHighlight(cmd.colour);
+ for each (var ent in cmd.entities)
+ {
+ var cmpSelectable = Engine.QueryInterface(ent, IID_Selectable);
+ if (cmpSelectable)
+ cmpSelectable.SetSelectionHighlight(cmd.colour);
+ }
};
GuiInterface.prototype.SetBuildingPlacementPreview = function(player, cmd)
diff --git a/source/graphics/Camera.h b/source/graphics/Camera.h
index 1dd2da1698..1d6df775b8 100644
--- a/source/graphics/Camera.h
+++ b/source/graphics/Camera.h
@@ -55,7 +55,7 @@ class CCamera
// everytime the view or projection matrices are
// altered.
void UpdateFrustum ();
- CFrustum GetFrustum () { return m_ViewFrustum; }
+ const CFrustum& GetFrustum () { return m_ViewFrustum; }
void SetViewPort (SViewPort *viewport);
const SViewPort& GetViewPort () const { return m_ViewPort; }
diff --git a/source/gui/scripting/ScriptFunctions.cpp b/source/gui/scripting/ScriptFunctions.cpp
index 5d774cb7f3..89e57dd937 100644
--- a/source/gui/scripting/ScriptFunctions.cpp
+++ b/source/gui/scripting/ScriptFunctions.cpp
@@ -163,6 +163,11 @@ std::vector PickEntitiesAtPoint(void* UNUSED(cbdata), int x, int y)
return EntitySelection::PickEntitiesAtPoint(*g_Game->GetSimulation2(), *g_Game->GetView()->GetCamera(), x, y);
}
+std::vector PickFriendlyEntitiesInRect(void* UNUSED(cbdata), int x0, int y0, int x1, int y1, int player)
+{
+ return EntitySelection::PickEntitiesInRect(*g_Game->GetSimulation2(), *g_Game->GetView()->GetCamera(), x0, y0, x1, y1, player);
+}
+
CFixedVector3D GetTerrainAtPoint(void* UNUSED(cbdata), int x, int y)
{
CVector3D pos = g_Game->GetView()->GetCamera()->GetWorldCoordinates(x, y, false);
@@ -200,6 +205,7 @@ void GuiScriptingInit(ScriptInterface& scriptInterface)
// Entity picking
scriptInterface.RegisterFunction, int, int, &PickEntitiesAtPoint>("PickEntitiesAtPoint");
+ scriptInterface.RegisterFunction, int, int, int, int, int, &PickFriendlyEntitiesInRect>("PickFriendlyEntitiesInRect");
scriptInterface.RegisterFunction("GetTerrainAtPoint");
// Misc functions
diff --git a/source/ps/Interact.cpp b/source/ps/Interact.cpp
index 27f5f4a4b3..c510a5fd7a 100644
--- a/source/ps/Interact.cpp
+++ b/source/ps/Interact.cpp
@@ -54,6 +54,7 @@
#include "simulation/FormationManager.h"
#include "simulation/Simulation.h"
#include "simulation/TerritoryManager.h"
+#include "simulation2/Simulation2.h"
#include "ps/CLogger.h"
#define LOG_CATEGORY L"world"
@@ -1139,6 +1140,9 @@ void MouseButtonUpHandler(const SDL_Event_* ev, int clicks)
InReaction InteractInputHandler( const SDL_Event_* ev )
{
+ if (g_UseSimulation2)
+ return IN_PASS;
+
if (!g_app_has_focus || !g_Game)
return IN_PASS;
diff --git a/source/renderer/OverlayRenderer.cpp b/source/renderer/OverlayRenderer.cpp
index 068f1c9dac..268a7651e3 100644
--- a/source/renderer/OverlayRenderer.cpp
+++ b/source/renderer/OverlayRenderer.cpp
@@ -65,6 +65,7 @@ void OverlayRenderer::RenderOverlays()
}
glDisable(GL_TEXTURE_2D);
+ glEnable(GL_BLEND);
for (size_t i = 0; i < m->lines.size(); ++i)
{
@@ -79,4 +80,6 @@ void OverlayRenderer::RenderOverlays()
glInterleavedArrays(GL_V3F, sizeof(float)*3, &line->m_Coords[0]);
glDrawArrays(GL_LINE_STRIP, 0, line->m_Coords.size()/3);
}
+
+ glDisable(GL_BLEND);
}
diff --git a/source/scriptinterface/ScriptInterface.h b/source/scriptinterface/ScriptInterface.h
index 1fcbb9ee97..e187526d79 100644
--- a/source/scriptinterface/ScriptInterface.h
+++ b/source/scriptinterface/ScriptInterface.h
@@ -32,7 +32,7 @@
// Set the maximum number of function arguments that can be handled
// (This should be as small as possible (for compiler efficiency),
// but as large as necessary for all wrapped functions)
-#define SCRIPT_INTERFACE_MAX_ARGS 5
+#define SCRIPT_INTERFACE_MAX_ARGS 6
struct ScriptInterface_impl;
class ScriptClass;
diff --git a/source/simulation2/components/CCmpVisualActor.cpp b/source/simulation2/components/CCmpVisualActor.cpp
index a2a86ef732..b3eb6ccde3 100644
--- a/source/simulation2/components/CCmpVisualActor.cpp
+++ b/source/simulation2/components/CCmpVisualActor.cpp
@@ -147,6 +147,13 @@ public:
return m_Unit->GetModel()->GetBounds();
}
+ virtual CVector3D GetPosition()
+ {
+ if (!m_Unit)
+ return CVector3D(0, 0, 0);
+ return m_Unit->GetModel()->GetTransform().GetTranslation();
+ }
+
virtual void SelectAnimation(std::string name, bool once, float speed)
{
if (!m_Unit)
diff --git a/source/simulation2/components/ICmpVisual.h b/source/simulation2/components/ICmpVisual.h
index 65598a2aba..8aa3f11efa 100644
--- a/source/simulation2/components/ICmpVisual.h
+++ b/source/simulation2/components/ICmpVisual.h
@@ -34,6 +34,19 @@ public:
*/
virtual CBound GetBounds() = 0;
+ /**
+ * Get the world-space position of the base point of the object's visual representation.
+ * (Not safe for use in simulation code.)
+ */
+ virtual CVector3D GetPosition() = 0;
+
+ /**
+ * Start playing the given animation. If there are multiple possible animations then it will
+ * pick one at random (not network-synchronised).
+ * @param name animation name (e.g. "idle", "walk", "melee"; the names are determined by actor XML files)
+ * @param once if true then the animation will play once and freeze at the final frame, else it will loop
+ * @param speed some kind of animation speed multiplier (TODO: work out exactly what the scale is)
+ */
virtual void SelectAnimation(std::string name, bool once, float speed) = 0;
DECLARE_INTERFACE_TYPE(Visual)
diff --git a/source/simulation2/helpers/Selection.cpp b/source/simulation2/helpers/Selection.cpp
index 3df2715158..afb5e081cc 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/ICmpOwnership.h"
#include "simulation2/components/ICmpSelectable.h"
#include "simulation2/components/ICmpVisual.h"
@@ -69,3 +70,50 @@ std::vector EntitySelection::PickEntitiesAtPoint(CSimulation2& simu
hitEnts.push_back(hits[i].second);
return hitEnts;
}
+
+std::vector EntitySelection::PickEntitiesInRect(CSimulation2& simulation, CCamera& camera, int sx0, int sy0, int sx1, int sy1, int owner)
+{
+ // Make sure sx0 <= sx1, and sy0 <= sy1
+ if (sx0 > sx1)
+ std::swap(sx0, sx1);
+ if (sy0 > sy1)
+ std::swap(sy0, sy1);
+
+ std::vector hitEnts;
+
+ const CSimulation2::InterfaceList& ents = simulation.GetEntitiesWithInterface(IID_Selectable);
+ for (CSimulation2::InterfaceList::const_iterator it = ents.begin(); it != ents.end(); ++it)
+ {
+ entity_id_t ent = it->first;
+
+ // Ignore entities not owned by 'owner'
+ CmpPtr cmpOwnership(simulation.GetSimContext(), ent);
+ if (cmpOwnership.null() || cmpOwnership->GetOwner() != owner)
+ continue;
+
+ // Find the current interpolated model position.
+ // (We just use the centre position and not the whole bounding box, because maybe
+ // that's better for users trying to select objects in busy areas)
+
+ 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;
+
+ // Compare screen-space coordinates
+ float x, y;
+ camera.GetScreenCoordinates(position, x, y);
+ int ix = (int)x;
+ int iy = (int)y;
+ if (sx0 <= ix && ix <= sx1 && sy0 <= iy && iy <= sy1)
+ hitEnts.push_back(ent);
+ }
+
+ return hitEnts;
+}
diff --git a/source/simulation2/helpers/Selection.h b/source/simulation2/helpers/Selection.h
index fc7e516f12..14726a6cbe 100644
--- a/source/simulation2/helpers/Selection.h
+++ b/source/simulation2/helpers/Selection.h
@@ -39,6 +39,13 @@ namespace EntitySelection
*/
std::vector PickEntitiesAtPoint(CSimulation2& simulation, CCamera& camera, int screenX, int screenY);
+/**
+ * Finds all selectable entities within the given screen coordinate rectangle,
+ * that belong to player @p owner.
+ * Returns unordered list.
+ */
+std::vector PickEntitiesInRect(CSimulation2& simulation, CCamera& camera, int sx0, int sy0, int sx1, int sy1, int owner);
+
} // namespace
#endif // INCLUDED_SELECTION