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_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 var maxDragDelta = 4; @@ -525,6 +533,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) { @@ -811,7 +829,7 @@ case "mousebuttondown": if (ev.button == SDL_BUTTON_LEFT) { - dragStart = [ ev.x, ev.y ]; + dragStart = [ev.x, ev.y]; inputState = INPUT_SELECTING; // If a single click occured, reset the clickedEntity. // Also set it if we're double/triple clicking and missed the unit earlier. @@ -821,10 +839,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; @@ -989,6 +1005,32 @@ } 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) + return doAction(action, ev); + } + break; + } + break; + case INPUT_BUILDING_PLACEMENT: switch (ev.type) { @@ -1113,6 +1155,105 @@ 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 = Engine.GetTerrainAtScreenPoint(ev.x, ev.y); + let p = new Vector2D(target.x, target.z); + if (!g_FreehandSelection_InputLine.length || + p.distanceTo(g_FreehandSelection_InputLine[g_FreehandSelection_InputLine.length - 1]) >= g_FreehandSelection_ResolutionInputLine) + g_FreehandSelection_InputLine.push(p); + 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 d = -distanceBetweenEnts; + + for (let i = 1; i < inputLine.length; ++i) + { + let p1 = inputLine[i]; + d += inputLine[i-1].distanceTo(p1); + + while (d >= 0) + { + p0 = Vector2D.sub(p0, p1).normalize().mult(d).add(p1); + entityDistribution.push(p0); + d -= 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 = Math.euclidDistance2D(entityDistribution[0].x, entityDistribution[0].y, firstUnit.position.x, firstUnit.position.z); + let firstToLastDist = Math.euclidDistance2D(entityDistribution[selection.length-1].x, entityDistribution[selection.length-1].y, firstUnit.position.x, firstUnit.position.z); + let lastToFirstDist = Math.euclidDistance2D(entityDistribution[0].x, entityDistribution[0].y, lastUnit.position.x, lastUnit.position.z); + let lastToLastDist = Math.euclidDistance2D(entityDistribution[selection.length-1].x, entityDistribution[selection.length-1].y, lastUnit.position.x, lastUnit.position.z); + + if (((firstToFirstDist < firstToLastDist) && (lastToFirstDist > lastToLastDist)) || + ((firstToFirstDist < firstToLastDist) && (firstToFirstDist > lastToFirstDist)) || + ((lastToLastDist < lastToFirstDist) && (lastToLastDist > firstToLastDist))) + { + for (let i in entityDistribution) + { + Engine.PostNetworkCommand({ + "type": "walk", + "entities": [selection[i]], + "x": entityDistribution[i].x, + "z": entityDistribution[i].y, + "queued": false + }); + } + } + else + { + 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