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; @@ -39,6 +41,12 @@ var mouseY = 0; var mouseIsOverObject = false; +// Input line +var g_InputLine = []; +var g_ResolutionInputLine = 1; +var g_MinLengthOfLine = 8; +var g_MinNumberOfUnits = 4; // Minmum must be 2 + // Number of pixels the mouse can move before the action is considered a drag var maxDragDelta = 4; @@ -577,6 +585,17 @@ } break; + case INPUT_UNIT_POSITION: + switch (ev.type) + { + case "mousemotion": + return positionUnitsFreehandSelectionMousemove(ev); + case "mousebuttonup": + return positionUnitsFreehandSelectionMouseUp(ev); + break; + } + break; + case INPUT_BUILDING_CLICK: switch (ev.type) { @@ -874,10 +893,8 @@ } else if (ev.button == SDL_BUTTON_RIGHT) { - var action = determineAction(ev.x, ev.y); - if (!action) - break; - return doAction(action, ev); + dragStart = [ ev.x, ev.y ]; + inputState = INPUT_RIGHT; } break; @@ -1042,6 +1059,33 @@ } break; + case INPUT_RIGHT: + switch (ev.type) + { + case "mousemotion": + // If the mouse moved further than a limit, switch to unit position mode + let dragDeltaX = ev.x - dragStart[0]; + let dragDeltaY = ev.y - dragStart[1]; + + if (Math.abs(dragDeltaX) >= maxDragDelta || Math.abs(dragDeltaY) >= 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) + break; + return doAction(action, ev); + } + break; + } + break; + case INPUT_BUILDING_PLACEMENT: switch (ev.type) { @@ -1150,6 +1194,123 @@ return false; } +function positionUnitsFreehandSelectionMousemove(ev) +{ + // Convert the input line into a List of points, + // for better performance the points have to have a min. distance to each other + let target = Engine.GetTerrainAtScreenPoint(ev.x, ev.y); + if (g_InputLine.length < 1) + g_InputLine.push(new Vector2D(target.x, target.z)); + else + { + let p = new Vector2D(target.x, target.z); + + if (p.distanceTo(g_InputLine[g_InputLine.length - 1]) >= g_ResolutionInputLine) + g_InputLine.push(p); + } + return false; +} + +function positionUnitsFreehandSelectionMouseUp(ev) +{ + inputState = INPUT_NORMAL; + let inputLine = g_InputLine; + g_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_MinLengthOfLine || selection.length < g_MinNumberOfUnits) + { + var action = determineAction(ev.x, ev.y); + if (action) + return doAction(action, ev); + return false; + } + else + { + // Even distribution of the units on the line + let reworkedLine = []; + let shouldDist = lengthOfLine / (selection.length - 1); + let p0 = inputLine[0]; + let d = - shouldDist; + reworkedLine.push(inputLine[0]); + + for (let i = 1; i < inputLine.length; ++i) + { + let p1 = inputLine[i]; + d += inputLine[i-1].distanceTo(inputLine[i]); + + while (d >= 0) + { + let dist = p0.distanceTo(p1); + p0 = p0.clone().sub(p1).mult(d / dist).add(p1); + reworkedLine.push(p0); + d -= shouldDist; + } + } + + // Rounding errors can lead to missing points + if (selection.length > reworkedLine.length) + reworkedLine.push(inputLine[inputLine.length - 1]); + + // Get the minimum Distance for each point to the units + for (let i = 0; i < reworkedLine.length; ++i) + { + let minDistUnit = getTheNextUnitToThePoint(selection, reworkedLine, i); + reworkedLine[i].min = minDistUnit.minDist; + reworkedLine[i].id = minDistUnit.idUnit; + } + + //sort the array on his minimum Distance, the largest first + reworkedLine.sort((a, b) => b.min - a.min); + + // Every point get his unit. The largest min. Distance at first. + // If the unit is even selected, the point searchs for a new one + for (let i = 0; i < reworkedLine.length; ++i) + { + let idUnit = reworkedLine[i].id; + + if (selection.indexOf(idUnit) == -1) + idUnit = getTheNextUnitToThePoint(selection, reworkedLine, i).idUnit; + + Engine.PostNetworkCommand({ + "type": "walk", + "entities": [idUnit], + "x": reworkedLine[i].x, + "z": reworkedLine[i].y, + "queued": false + }); + selection.splice(selection.indexOf(idUnit), 1); + } + } + } + return true; +} + +function getTheNextUnitToThePoint(selection, reworkedLine, i) +{ + let minDist; + let idUnit; + for (let j = 0; j < selection.length; ++j) + { + let unit = GetEntityState(selection[j]); + let currentDist = reworkedLine[i].distanceTo(new Vector2D(unit.position.x, unit.position.z)); + + if (minDist == null || minDist > currentDist) + { + minDist = currentDist; + idUnit = selection[j]; + } + } + return {minDist, idUnit}; +} + function handleMinimapEvent(target) { // Partly duplicated from handleInputAfterGui(), but with the input being