From ff81dee18cdacb9913a87b3a31b5debe009b0d38 Mon Sep 17 00:00:00 2001 From: "(Jip) Willem Wijnia" Date: Thu, 3 Oct 2024 20:26:48 +0200 Subject: [PATCH 1/5] Implement basic gesture detection to delete commands --- engine/User.lua | 3 +- lua/options/options.lua | 29 +++++ lua/ui/game/cursor/DeleteBuildCommand.lua | 146 ++++++++++++++++++++++ lua/ui/game/gamemain.lua | 1 + 4 files changed, 178 insertions(+), 1 deletion(-) create mode 100644 lua/ui/game/cursor/DeleteBuildCommand.lua diff --git a/engine/User.lua b/engine/User.lua index ce06129cc0..f938766a13 100644 --- a/engine/User.lua +++ b/engine/User.lua @@ -7,6 +7,7 @@ ---@field targetId? EntityId ---@field blueprintId? UnitId ---@field commandType number +---@field commandId number ---@alias SubmergeStatus ---| -1 # submerged @@ -480,7 +481,7 @@ end --- ---@param key string ----@return string[] +---@return string function GetOptions(key) end diff --git a/lua/options/options.lua b/lua/options/options.lua index c4089874d0..b2336bc978 100644 --- a/lua/options/options.lua +++ b/lua/options/options.lua @@ -1391,6 +1391,35 @@ options = { end end, }, + + { + title = 'Gesture control', + type = 'header', + + -- these are expected everywhere + default = '', + key = '', + }, + + { + title = "Gesture to delete commands", + key = 'gesture_delete_commands', + type = 'toggle', + default = 'off', + custom = { + states = { + { text = "", key = 'off' }, + { text = "Build commands only", key = 'build' }, + { text = "Engineer related commands only", key = 'engineering' }, + { text = "All commands", key = 'all' }, + }, + }, + + set = function(control, value, startup) + LOG(value) + import("/lua/ui/game/cursor/deletebuildcommand.lua").SetGestureDetectionCommandType(value) + end, + }, }, }, video = { diff --git a/lua/ui/game/cursor/DeleteBuildCommand.lua b/lua/ui/game/cursor/DeleteBuildCommand.lua new file mode 100644 index 0000000000..30159b924d --- /dev/null +++ b/lua/ui/game/cursor/DeleteBuildCommand.lua @@ -0,0 +1,146 @@ +--****************************************************************************************************** +--** Copyright (c) 2024 FAForever +--** +--** Permission is hereby granted, free of charge, to any person obtaining a copy +--** of this software and associated documentation files (the "Software"), to deal +--** in the Software without restriction, including without limitation the rights +--** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +--** copies of the Software, and to permit persons to whom the Software is +--** furnished to do so, subject to the following conditions: +--** +--** The above copyright notice and this permission notice shall be included in all +--** copies or substantial portions of the Software. +--** +--** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +--** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +--** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +--** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +--** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +--** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +--** SOFTWARE. +--****************************************************************************************************** + +---@alias UIGestureDetectionCommandType 'off' | 'build' | 'engineering' | 'all' + +--- See also: https://github.com/FAForever/FA-Binary-Patches/pull/22 +local BuildCommandTypes = { + [8] = true, -- BuildMobile + [9] = true, -- BuildAssist +} + +--- See also: https://github.com/FAForever/FA-Binary-Patches/pull/22 +local EngineeringCommandTypes = { + [8] = true, -- BuildMobile + [9] = true, -- BuildAssist + [15] = true, -- Guard + [19] = true, -- Reclaim + [20] = true, -- Repair + [21] = true, -- Capture + [32] = true, -- Sacrifice +} + +---@type UIGestureDetectionCommandType +local GestureDetectionCommandType = import("/lua/user/prefs.lua").GetOption('gesture_delete_commands') + +---@type number +local GestureDetectionSuccessionThreshold = 3 + +---@type thread? +local GestureDetectionThreadInstance = nil; + +--- Starts a basic gesture detection thread to delete a (build) command +local GestureDetectionThread = function() + + local gestureStart = 0 + local gestureTargetId = nil + local gestureSuccessive = 0 + local oldCommand = nil + + while true do + WaitFrames(1) + + -- check for early exit + if GestureDetectionCommandType == 'off' then + return + end + + -- retrieve a command + local command = GetHighlightCommand() + + -- only register the event when we just started hovering over a command + if command and not oldCommand then + -- check for early exit + if GestureDetectionCommandType == 'build' and not BuildCommandTypes[command.commandType] then + gestureTargetId = nil + continue + elseif GestureDetectionCommandType == 'engineering' and not EngineeringCommandTypes[command.commandType] then + gestureTargetId = nil + continue + end + + -- keep track of the gesture + local gameTimeSeconds = GetGameTimeSeconds() + if command.commandId == gestureTargetId and gameTimeSeconds - gestureStart < 0.5 then + gestureSuccessive = gestureSuccessive + 1 + gestureStart = gameTimeSeconds + else + gestureStart = gameTimeSeconds + gestureTargetId = command.commandId + gestureSuccessive = 0 + end + + if gestureSuccessive == GestureDetectionSuccessionThreshold then + DeleteCommand(gestureTargetId) + gestureTargetId = 0 + end + end + + oldCommand = command + end +end + +---@return thread +StartGestureDetectionThread = function() + -- prevent duplicate threads + if GestureDetectionThreadInstance and type(GestureDetectionThreadInstance) == "thread" then + KillThread(GestureDetectionThreadInstance) + end + + GestureDetectionThreadInstance = ForkThread(GestureDetectionThread) + return GestureDetectionThreadInstance +end + +---@param commandType UIGestureDetectionCommandType +SetGestureDetectionCommandType = function(commandType) + GestureDetectionCommandType = commandType + StartGestureDetectionThread() +end + +------------------------------------------------------------------------------- +--#region Debugging + +--- Called by the module manager when this module is reloaded +---@param newModule any +function __moduleinfo.OnReload(newModule) + newModule.StartGestureDetectionThread() +end + +--- Called by the module manager when this module becomes dirty +function __moduleinfo.OnDirty() + reprsl(__moduleinfo, { depth = 2 }) + + -- kill current thread + if GestureDetectionThreadInstance and type(GestureDetectionThreadInstance) == "thread" then + KillThread(GestureDetectionThreadInstance) + end + + -- trigger a reload + ForkThread( + function() + WaitSeconds(1.0) + import(__moduleinfo.name) + end + ) +end + +--#endregion diff --git a/lua/ui/game/gamemain.lua b/lua/ui/game/gamemain.lua index 06e6ac4ddc..d3201dc26b 100644 --- a/lua/ui/game/gamemain.lua +++ b/lua/ui/game/gamemain.lua @@ -164,6 +164,7 @@ function CreateUI(isReplay) import("/lua/system/performance.lua") import("/lua/ui/game/cursor/depth.lua") import("/lua/ui/game/cursor/hover.lua") + import("/lua/ui/game/cursor/DeleteBuildCommand.lua").StartGestureDetectionThread() -- casting tools From fe7db5b3086a8fbd820620046bc90cdc4321f9eb Mon Sep 17 00:00:00 2001 From: "(Jip) Willem Wijnia" Date: Fri, 4 Oct 2024 12:17:35 +0200 Subject: [PATCH 2/5] Remove a LOG statement --- lua/options/options.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/lua/options/options.lua b/lua/options/options.lua index b2336bc978..6090466abc 100644 --- a/lua/options/options.lua +++ b/lua/options/options.lua @@ -1416,7 +1416,6 @@ options = { }, set = function(control, value, startup) - LOG(value) import("/lua/ui/game/cursor/deletebuildcommand.lua").SetGestureDetectionCommandType(value) end, }, From b8fc7cdec2dc5091d1ae40412c721f612d98b0f1 Mon Sep 17 00:00:00 2001 From: "(Jip) Willem Wijnia" Date: Fri, 4 Oct 2024 12:47:35 +0200 Subject: [PATCH 3/5] Fix game crash on start up --- lua/options/options.lua | 4 +++- lua/ui/game/cursor/DeleteBuildCommand.lua | 28 ++++++++++------------- 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/lua/options/options.lua b/lua/options/options.lua index 6090466abc..222f59b75d 100644 --- a/lua/options/options.lua +++ b/lua/options/options.lua @@ -1416,7 +1416,9 @@ options = { }, set = function(control, value, startup) - import("/lua/ui/game/cursor/deletebuildcommand.lua").SetGestureDetectionCommandType(value) + if SessionIsActive() then + import("/lua/ui/game/cursor/deletebuildcommand.lua").SetGestureDetectionCommandType(value) + end end, }, }, diff --git a/lua/ui/game/cursor/DeleteBuildCommand.lua b/lua/ui/game/cursor/DeleteBuildCommand.lua index 30159b924d..ff3c24f916 100644 --- a/lua/ui/game/cursor/DeleteBuildCommand.lua +++ b/lua/ui/game/cursor/DeleteBuildCommand.lua @@ -22,6 +22,9 @@ ---@alias UIGestureDetectionCommandType 'off' | 'build' | 'engineering' | 'all' +---@type TrashBag +local ModuleTrash = TrashBag() + --- See also: https://github.com/FAForever/FA-Binary-Patches/pull/22 local BuildCommandTypes = { [8] = true, -- BuildMobile @@ -45,9 +48,6 @@ local GestureDetectionCommandType = import("/lua/user/prefs.lua").GetOption('ges ---@type number local GestureDetectionSuccessionThreshold = 3 ----@type thread? -local GestureDetectionThreadInstance = nil; - --- Starts a basic gesture detection thread to delete a (build) command local GestureDetectionThread = function() @@ -88,7 +88,7 @@ local GestureDetectionThread = function() gestureTargetId = command.commandId gestureSuccessive = 0 end - + if gestureSuccessive == GestureDetectionSuccessionThreshold then DeleteCommand(gestureTargetId) gestureTargetId = 0 @@ -99,15 +99,16 @@ local GestureDetectionThread = function() end end ----@return thread +--- Starts the gesture detection thread. Function is idempotent. StartGestureDetectionThread = function() - -- prevent duplicate threads - if GestureDetectionThreadInstance and type(GestureDetectionThreadInstance) == "thread" then - KillThread(GestureDetectionThreadInstance) + ModuleTrash:Destroy() + + -- logic requires an active session + if SessionIsActive() then + return end - GestureDetectionThreadInstance = ForkThread(GestureDetectionThread) - return GestureDetectionThreadInstance + ModuleTrash:Add(ForkThread(GestureDetectionThread)) end ---@param commandType UIGestureDetectionCommandType @@ -127,12 +128,7 @@ end --- Called by the module manager when this module becomes dirty function __moduleinfo.OnDirty() - reprsl(__moduleinfo, { depth = 2 }) - - -- kill current thread - if GestureDetectionThreadInstance and type(GestureDetectionThreadInstance) == "thread" then - KillThread(GestureDetectionThreadInstance) - end + ModuleTrash:Destroy() -- trigger a reload ForkThread( From 2f92b47b097bbb042f47eb7ff4917a99bfef1708 Mon Sep 17 00:00:00 2001 From: "(Jip) Willem Wijnia" Date: Fri, 4 Oct 2024 12:48:33 +0200 Subject: [PATCH 4/5] Local scope for performance --- lua/ui/game/cursor/DeleteBuildCommand.lua | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lua/ui/game/cursor/DeleteBuildCommand.lua b/lua/ui/game/cursor/DeleteBuildCommand.lua index ff3c24f916..7d6d4a6316 100644 --- a/lua/ui/game/cursor/DeleteBuildCommand.lua +++ b/lua/ui/game/cursor/DeleteBuildCommand.lua @@ -51,6 +51,13 @@ local GestureDetectionSuccessionThreshold = 3 --- Starts a basic gesture detection thread to delete a (build) command local GestureDetectionThread = function() + -- local scope for performance + local WaitFrames = WaitFrames + local GetHighlightCommand = GetHighlightCommand + local GetGameTimeSeconds = GetGameTimeSeconds + local DeleteCommand = DeleteCommand + + -- internal state local gestureStart = 0 local gestureTargetId = nil local gestureSuccessive = 0 From a317e7ea7c09475855aa0ea565a388554722486a Mon Sep 17 00:00:00 2001 From: "(Jip) Willem Wijnia" Date: Fri, 4 Oct 2024 12:49:11 +0200 Subject: [PATCH 5/5] Add documentation --- lua/ui/game/cursor/DeleteBuildCommand.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/lua/ui/game/cursor/DeleteBuildCommand.lua b/lua/ui/game/cursor/DeleteBuildCommand.lua index 7d6d4a6316..a24a49fac3 100644 --- a/lua/ui/game/cursor/DeleteBuildCommand.lua +++ b/lua/ui/game/cursor/DeleteBuildCommand.lua @@ -118,6 +118,7 @@ StartGestureDetectionThread = function() ModuleTrash:Add(ForkThread(GestureDetectionThread)) end +--- Sets the gesture detection command type and attempts to restart the gesture detection thread. ---@param commandType UIGestureDetectionCommandType SetGestureDetectionCommandType = function(commandType) GestureDetectionCommandType = commandType