do LIBRARY_ClickAndHoldMovement = true
local SCOPE_PREFIX = "ClickAndHoldMovement_" ---@type string
--[[
===========================================================================================
Click-And-Hold Movement
by Antares
Enable click-and-hold movement (hold the right mouse-button to move your hero) for hero maps
like aeon of strife, hero arenas, or rpgs.
Requires totalInitialization.
===========================================================================================
]]
--====================================================
--Config
--====================================================
local IGNORE_MOUSE_SPELL_DURATION = 0.5 ---@type number --Disables the click-and-hold trigger for this many seconds when a unit in the selected group casts a spell. This enables the user to cast a spell without releasing the right mouse-button.
local IGNORE_MOUSE_CLICK_DURATION = 0.25 ---@type number --Disables the click-and-hold trigger for this many seconds right after the right mouse-button is pressed. This prevents the trigger from firing on a simple right-click.
local MOUSE_MOVE_COOLDOWN_DURATION = 0.1 ---@type number --Duration of the cooldown on the click-and-hold trigger on giving orders.
local DISABLE_IF_MULTIPLE_SELECTED = false ---@type boolean
local STOP_MOVING_ON_RELEASE = false ---@type boolean --The commanded units will stop moving as soon as the right mouse-button is released. They will also ignore pathing blockers between the mouse-cursor and the unit.
local CUSTOM_CALLBACK = false ---@type boolean --Enable if you want to write your own callback function for the click-and-hold trigger. Otherwise, it will be a replaced by a simple smart command.
--====================================================
local mouseMoveTrigger={} ---@type trigger[]
MouseX = {} ---@type number[]
MouseY = {} ---@type number[]
local rightClickOn = {} ---@type boolean[]
local ignoreMouseTimer = {} ---@type timer[]
local data = {}
local selectedUnits={} ---@type group[]
local setGroup = {} ---@type boolean[]
local noOrders = {} ---@type boolean[]
--====================================================
---@param whichUnit unit
---@param x number
---@param y number
local function CustomCallback(whichUnit, x, y)
--Define your custom callback code here.
end
--====================================================
---@param P player
---@return boolean
local function SetAndCheckSelection(P)
local invalidSelection ---@type boolean
local first ---@type unit
selectedUnits[P] = CreateGroup()
GroupEnumUnitsSelected(selectedUnits[P] , P , nil)
if DISABLE_IF_MULTIPLE_SELECTED then
invalidSelection = BlzGroupGetSize(selectedUnits[P]) ~= 1
else
invalidSelection = BlzGroupGetSize(selectedUnits[P]) == 0
end
setGroup[P] = false
if invalidSelection then
DestroyGroup(selectedUnits[P])
return false
else
first = FirstOfGroup(selectedUnits[P])
if GetOwningPlayer(first) == P or (IsUnitAlly(first,P) and GetPlayerAlliance(GetOwningPlayer(first),P,ALLIANCE_SHARED_CONTROL)) then
return true
else
DestroyGroup(selectedUnits[P])
return false
end
end
end
---@param P player
local function CommandGroup(P)
local i = BlzGroupGetSize(selectedUnits[P]) - 1 ---@type integer
local u ---@type unit
local angle ---@type number
while i >= 0 do
if CUSTOM_CALLBACK then
CustomCallback( BlzGroupUnitAt(selectedUnits[P] , i) , MouseX[P] , MouseY[P] )
elseif STOP_MOVING_ON_RELEASE then
u = BlzGroupUnitAt(selectedUnits[P] , i)
angle = Atan2( MouseY[P] - GetUnitY(u) , MouseX[P] - GetUnitX(u) )
IssuePointOrderById( BlzGroupUnitAt(selectedUnits[P] , i) , 851971 , GetUnitX(u) + 50*Cos(angle) , GetUnitY(u) + 50*Sin(angle) )
else
IssuePointOrderById( BlzGroupUnitAt(selectedUnits[P] , i) , 851971 , MouseX[P] , MouseY[P] )
end
i = i - 1
end
end
local function Enable()
local P = data[GetExpiredTimer()] ---@type player
noOrders[P] = false
if rightClickOn[P] then
if setGroup[P] then
if SetAndCheckSelection(P) then
CommandGroup(P)
end
else
CommandGroup(P)
end
end
end
local function MouseSpellIgnore()
local whichUnit = GetSpellAbilityUnit() ---@type unit
local P = GetOwningPlayer(whichUnit) ---@type player
if IsUnitInGroup(whichUnit , selectedUnits[P]) then
noOrders[P] = true
TimerStart( ignoreMouseTimer[P] , IGNORE_MOUSE_SPELL_DURATION , false , Enable )
end
end
local function MouseMove()
local P = GetTriggerPlayer() ---@type player
local mX = BlzGetTriggerPlayerMouseX() ---@type number
local mY = BlzGetTriggerPlayerMouseY() ---@type number
if mY ~= 0 or mX ~= 0 then
MouseX[P] = mX
MouseY[P] = mY
end
if noOrders[P] then
return
end
if MOUSE_MOVE_COOLDOWN_DURATION > 0 then
noOrders[P] = true
TimerStart( ignoreMouseTimer[P] , MOUSE_MOVE_COOLDOWN_DURATION , false , Enable )
end
CommandGroup(P)
end
local function MouseRightClick()
local P ---@type player
if BlzGetTriggerPlayerMouseButton() == MOUSE_BUTTON_TYPE_RIGHT then
P = GetTriggerPlayer()
rightClickOn[P] = true
noOrders[P] = true
setGroup[P] = true
MouseX[P] = BlzGetTriggerPlayerMouseX()
MouseY[P] = BlzGetTriggerPlayerMouseY()
TimerStart( ignoreMouseTimer[P] , IGNORE_MOUSE_CLICK_DURATION , false , Enable )
EnableTrigger(mouseMoveTrigger[P])
end
end
local function MouseRightRelease()
local P ---@type player
if BlzGetTriggerPlayerMouseButton() == MOUSE_BUTTON_TYPE_RIGHT then
P = GetTriggerPlayer()
DestroyGroup(selectedUnits[P])
rightClickOn[P] = false
DisableTrigger(mouseMoveTrigger[P])
end
end
local function At0s()
local P ---@type player
local trigDown = CreateTrigger() ---@type trigger
local trigUp = CreateTrigger() ---@type trigger
local trigIgnore = CreateTrigger() ---@type trigger
for p = 0, 23 do
P = Player(p)
if GetPlayerSlotState(P) == PLAYER_SLOT_STATE_PLAYING and GetPlayerController(P) == MAP_CONTROL_USER then
mouseMoveTrigger[P] = CreateTrigger()
TriggerRegisterPlayerEvent( trigDown, P, EVENT_PLAYER_MOUSE_DOWN )
TriggerRegisterPlayerEvent( trigUp, P, EVENT_PLAYER_MOUSE_UP )
TriggerRegisterPlayerEvent( mouseMoveTrigger[P], P, EVENT_PLAYER_MOUSE_MOVE )
TriggerAddAction( mouseMoveTrigger[P] , MouseMove )
DisableTrigger(mouseMoveTrigger[P])
ignoreMouseTimer[P] = CreateTimer()
data[ignoreMouseTimer[P]] = P
end
p = p + 1
end
DestroyTrigger(GetTriggeringTrigger())
TriggerAddAction( trigDown , MouseRightClick )
TriggerAddAction( trigUp , MouseRightRelease )
TriggerRegisterAnyUnitEventBJ( trigIgnore, EVENT_PLAYER_UNIT_SPELL_CAST )
TriggerAddAction( trigIgnore , MouseSpellIgnore )
end
OnInit.global(function()
local at0sTrigger = CreateTrigger() ---@type trigger
TriggerRegisterTimerEvent( at0sTrigger , 0.0 , false )
TriggerAddAction( at0sTrigger , At0s )
end)
end