Index: binaries/data/mods/public/gui/session/input.js =================================================================== --- binaries/data/mods/public/gui/session/input.js +++ binaries/data/mods/public/gui/session/input.js @@ -30,6 +30,8 @@ const INPUT_BUILDING_WALL_CLICK = 8; const INPUT_BUILDING_WALL_PATHING = 9; const INPUT_MASSTRIBUTING = 10; +const INPUT_RIGHT = 11; +const INPUT_UNIT_POSITION = 12; var inputState = INPUT_NORMAL; @@ -40,6 +42,14 @@ var mouseIsOverObject = false; /** + * Input line + */ +var g_FreehandSelection_InputLine = []; +const g_FreehandSelection_ResolutionInputLine = 1; +const g_FreehandSelection_MinLengthOfLine = 8; +const g_FreehandSelection_MinNumberOfUnits = 2; // Minmum must be 2, for better performance set it higher + +/** * Number of pixels the mouse can move before the action is considered a drag. */ const g_MaxDragDelta = 4; @@ -539,6 +549,16 @@ } break; + case INPUT_UNIT_POSITION: + switch (ev.type) + { + case "mousemotion": + return positionUnitsFreehandSelectionMouseMove(ev); + case "mousebuttonup": + return positionUnitsFreehandSelectionMouseUp(ev); + } + break; + case INPUT_BUILDING_CLICK: switch (ev.type) { @@ -831,10 +851,8 @@ } else if (ev.button == SDL_BUTTON_RIGHT) { - var action = determineAction(ev.x, ev.y); - if (!action) - break; - return doAction(action, ev); + g_DragStart = new Vector2D(ev.x, ev.y); + inputState = INPUT_RIGHT; } break; @@ -996,6 +1014,29 @@ } break; + case INPUT_RIGHT: + switch (ev.type) + { + case "mousemotion": + // If the mouse moved further than a limit, switch to unit position mode + if (g_DragStart.distanceTo(ev) >= g_MaxDragDelta) + { + inputState = INPUT_UNIT_POSITION; + return false; + } + break; + case "mousebuttonup": + inputState = INPUT_NORMAL; + if (ev.button == SDL_BUTTON_RIGHT) + { + let action = determineAction(ev.x, ev.y); + if (action) + return doAction(action, ev); + } + break; + } + break; + case INPUT_BUILDING_PLACEMENT: switch (ev.type) { @@ -1120,6 +1161,90 @@ return false; } +function positionUnitsFreehandSelectionMouseMove(ev) +{ + // Convert the input line into a List of points, + // for better performance the points have to have a minimum distance to each other + let target = Vector2D.from3D(Engine.GetTerrainAtScreenPoint(ev.x, ev.y)); + if (!g_FreehandSelection_InputLine.length || + target.distanceTo(g_FreehandSelection_InputLine[g_FreehandSelection_InputLine.length - 1]) >= g_FreehandSelection_ResolutionInputLine) + g_FreehandSelection_InputLine.push(target); + return false; +} + +function positionUnitsFreehandSelectionMouseUp(ev) +{ + inputState = INPUT_NORMAL; + let inputLine = g_FreehandSelection_InputLine; + g_FreehandSelection_InputLine = []; + if (ev.button == SDL_BUTTON_RIGHT) + { + let lengthOfLine = 0; + for (let i = 1; i < inputLine.length; ++i) + lengthOfLine += inputLine[i].distanceTo(inputLine[i-1]); + + let selection = g_Selection.toList().filter(ent => GetEntityState(ent).unitAI); + + // If the line is to short the units will walk to the end of the line + if (lengthOfLine < g_FreehandSelection_MinLengthOfLine || selection.length < g_FreehandSelection_MinNumberOfUnits) + { + let action = determineAction(ev.x, ev.y); + return action && doAction(action, ev); + } + else + { + // Even distribution of the units on the line + let p0 = inputLine[0]; + let entityDistribution = [p0]; + let distanceBetweenEnts = lengthOfLine / (selection.length - 1); + let freeDist = -distanceBetweenEnts; + + for (let i = 1; i < inputLine.length; ++i) + { + let p1 = inputLine[i]; + freeDist += inputLine[i-1].distanceTo(p1); + + while (freeDist >= 0) + { + p0 = Vector2D.sub(p0, p1).normalize().mult(freeDist).add(p1); + entityDistribution.push(p0); + freeDist -= distanceBetweenEnts; + } + } + + // Rounding errors can lead to missing points + if (selection.length > entityDistribution.length) + entityDistribution.push(inputLine[inputLine.length - 1]); + + selection.sort((a, b) => a - b); + + let firstUnit = GetEntityState(selection[0]); + let lastUnit = GetEntityState(selection[selection.length-1]); + let firstToFirstDist = Vector2D.from3D(firstUnit.position).distanceTo(entityDistribution[0]); + let firstToLastDist = Vector2D.from3D(firstUnit.position).distanceTo(entityDistribution[selection.length-1]); + let lastToFirstDist = Vector2D.from3D(lastUnit.position).distanceTo(entityDistribution[0]); + let lastToLastDist = Vector2D.from3D(lastUnit.position).distanceTo(entityDistribution[selection.length-1]); + + if (firstToFirstDist > firstToLastDist && lastToFirstDist < lastToLastDist || + firstToFirstDist > firstToLastDist && firstToLastDist > lastToLastDist || + lastToLastDist > lastToFirstDist && lastToFirstDist > firstToFirstDist) + entityDistribution.sort((a, b) => b - a); + + for (let i in entityDistribution) + { + Engine.PostNetworkCommand({ + "type": "walk", + "entities": [selection[i]], + "x": entityDistribution[i].x, + "z": entityDistribution[i].y, + "queued": false + }); + } + } + } + return true; +} + function handleMinimapEvent(target) { // Partly duplicated from handleInputAfterGui(), but with the input being