Add BindAid

This commit is contained in:
dhert
2023-03-10 20:49:42 -06:00
parent a24dabe5b7
commit 299af7eaf4
14 changed files with 1808 additions and 0 deletions

View File

@@ -0,0 +1,322 @@
local keyman = require("KeybindManager")
local conf = require("BindAidConfig")
------------------------------------------
-- ModKey Overrides -- Client
--- All of the override functions ModKey Supports
------------------------------------------
local overrides = {}
-- We want the LAST references to all of this
overrides.onStart = function()
-- Don't do this if disabled
if not conf.Local.keyinputOptimize then return end
------------------------------------------
-- ISHotbar
---- "All hotbar keys"
------------------------------------------
local _ISHotbar_onKeyStartPressed = ISHotbar.onKeyStartPressed
local _ISHotbar_onKeyPressed = ISHotbar.onKeyPressed
local _ISHotbar_onKeyKeepPressed = ISHotbar.onKeyKeepPressed
Events.OnKeyStartPressed.Remove(_ISHotbar_onKeyStartPressed)
Events.OnKeyKeepPressed.Remove(_ISHotbar_onKeyKeepPressed)
Events.OnKeyPressed.Remove(_ISHotbar_onKeyPressed)
-- we need to get all of the keys that start with "Hotbar"
local hotbars = {}
for _,v in ipairs(keyBinding) do
if luautils.stringStarts(v.value, "Hotbar ") then
hotbars[#hotbars+1] = v.value
end
end
for _,v in ipairs(hotbars) do
keyman.addKeyEvents(v, {
["OnKeyStartPressed"] = _ISHotbar_onKeyStartPressed,
["OnKeyKeepPressed"] = _ISHotbar_onKeyKeepPressed,
["OnKeyPressed"] = _ISHotbar_onKeyPressed
})
end
hotbars = nil
------------------------------------------
-- ISVehicleMenu
---- "VehicleMechanics"
---- "StartVehicleEngine"
---- "VehicleHeater"
---- "VehicleHorn"
---- "VehicleSwitchSeat"
------------------------------------------
local _ISVehicleMenu_onKeyPressed = ISVehicleMenu.onKeyPressed
local _ISVehicleMenu_onKeyStartPressed = ISVehicleMenu.onKeyStartPressed
Events.OnKeyPressed.Remove(_ISVehicleMenu_onKeyPressed)
Events.OnKeyStartPressed.Remove(_ISVehicleMenu_onKeyStartPressed)
keyman.addKeyEvents("VehicleMechanics", {
["OnKeyPressed"] = _ISVehicleMenu_onKeyPressed
})
keyman.addKeyEvents("StartVehicleEngine", {
["OnKeyPressed"] = _ISVehicleMenu_onKeyPressed
})
keyman.addKeyEvents("VehicleHeater", {
["OnKeyPressed"] = _ISVehicleMenu_onKeyPressed
})
keyman.addKeyEvents("VehicleHorn", {
["OnKeyPressed"] = _ISVehicleMenu_onKeyPressed,
["OnKeyStartPressed"] = _ISVehicleMenu_onKeyStartPressed
})
keyman.addKeyEvents("VehicleSwitchSeat", {
["OnKeyStartPressed"] = _ISVehicleMenu_onKeyStartPressed
})
------------------------------------------
-- ISWorldMap
---- "Map"
------------------------------------------
local _ISWorldMap_onKeyStartPressed = ISWorldMap.onKeyStartPressed
local _ISWorldMap_onKeyKeepPressed = ISWorldMap.onKeyKeepPressed
local _ISWorldMap_onKeyReleased = ISWorldMap.onKeyReleased
Events.OnKeyStartPressed.Remove(_ISWorldMap_onKeyStartPressed)
Events.OnKeyKeepPressed.Remove(_ISWorldMap_onKeyKeepPressed)
Events.OnKeyPressed.Remove(_ISWorldMap_onKeyReleased)
keyman.addKeyEvents("Map", {
["OnKeyStartPressed"] = _ISWorldMap_onKeyStartPressed,
["OnKeyKeepPressed"] = _ISWorldMap_onKeyKeepPressed,
["OnKeyPressed"] = _ISWorldMap_onKeyReleased
})
------------------------------------------
-- ISLightSourceRadialMenu
---- "Equip/Turn On/Off Light Source"
------------------------------------------
local _ISLightSourceRadialMenu_onKeyPressed = ISLightSourceRadialMenu.onKeyPressed
local _ISLightSourceRadialMenu_onKeyRepeat = ISLightSourceRadialMenu.onKeyRepeat
local _ISLightSourceRadialMenu_onKeyReleased = ISLightSourceRadialMenu.onKeyReleased
Events.OnKeyStartPressed.Remove(_ISLightSourceRadialMenu_onKeyPressed)
Events.OnKeyKeepPressed.Remove(_ISLightSourceRadialMenu_onKeyRepeat)
Events.OnKeyPressed.Remove(_ISLightSourceRadialMenu_onKeyReleased)
keyman.addKeyEvents("Equip/Turn On/Off Light Source", {
["OnKeyStartPressed"] = _ISLightSourceRadialMenu_onKeyPressed,
["OnKeyKeepPressed"] = _ISLightSourceRadialMenu_onKeyRepeat,
["OnKeyPressed"] = _ISLightSourceRadialMenu_onKeyReleased
})
------------------------------------------
-- ISInventoryPage
---- "Toggle Inventory"
------------------------------------------
local _ISInventoryPage_onKeyPressed = ISInventoryPage.onKeyPressed
Events.OnKeyPressed.Remove(_ISInventoryPage_onKeyPressed)
keyman.addKeyEvents("Toggle Inventory", {
["OnKeyPressed"] = _ISInventoryPage_onKeyPressed
})
------------------------------------------
-- ISFPS
---- "Display FPS"
------------------------------------------
local _ISFPS_onKeyPressed = ISFPS.onKeyPressed
Events.OnKeyPressed.Remove(_ISFPS_onKeyPressed)
keyman.addKeyEvents("Display FPS", {
["OnKeyPressed"] = _ISFPS_onKeyPressed
})
------------------------------------------
-- ISUIHandler
---- "VehicleRadialMenu"
---- "Toggle UI"
---- "Show Ping"
------------------------------------------
local _ISUIHandler_onKeyStartPressed = ISUIHandler.onKeyStartPressed
local _ISUIHandler_onKeyPressed = ISUIHandler.onKeyPressed
Events.OnKeyStartPressed.Remove(_ISUIHandler_onKeyStartPressed)
Events.OnKeyPressed.Remove(_ISUIHandler_onKeyPressed)
keyman.addKeyEvents("Toggle UI", {
["OnKeyStartPressed"] = _ISUIHandler_onKeyStartPressed,
["OnKeyPressed"] = _ISUIHandler_onKeyPressed
})
keyman.addKeyEvents("VehicleRadialMenu", {
["OnKeyStartPressed"] = _ISUIHandler_onKeyStartPressed,
["OnKeyPressed"] = _ISUIHandler_onKeyPressed
})
------------------------------------------
-- ISCraftingUI
---- "Crafting UI"
------------------------------------------
local _ISCraftingUI_onPressKey = ISCraftingUI.onPressKey
Events.OnCustomUIKey.Remove(_ISCraftingUI_onPressKey)
keyman.addKeyEvents("Crafting UI", {
["OnCustomUIKey"] = _ISCraftingUI_onPressKey
})
------------------------------------------
-- ISSafetyUI
---- "Toggle Safety"
------------------------------------------
local _ISSafetyUI_onKeyPressed = ISSafetyUI.onKeyPressed
Events.OnKeyPressed.Remove(_ISSafetyUI_onKeyPressed)
if isClient() and getServerOptions():getBoolean("SafetySystem") then
keyman.addKeyEvents("Toggle Safety", {
["OnKeyPressed"] = _ISSafetyUI_onKeyPressed
})
end
------------------------------------------
-- ISFirearmRadialMenu
---- "ReloadWeapon"
------------------------------------------
local _ISFirearmRadialMenu_onKeyPressed = ISFirearmRadialMenu.onKeyPressed
local _ISFirearmRadialMenu_onKeyRepeat = ISFirearmRadialMenu.onKeyRepeat
local _ISFirearmRadialMenu_onKeyReleased = ISFirearmRadialMenu.onKeyReleased
Events.OnKeyStartPressed.Remove(_ISFirearmRadialMenu_onKeyPressed)
Events.OnKeyKeepPressed.Remove(_ISFirearmRadialMenu_onKeyRepeat)
Events.OnKeyPressed.Remove(_ISFirearmRadialMenu_onKeyReleased)
keyman.addKeyEvents("ReloadWeapon", {
["OnKeyStartPressed"] = _ISFirearmRadialMenu_onKeyPressed,
["OnKeyKeepPressed"] = _ISFirearmRadialMenu_onKeyRepeat,
["OnKeyPressed"] = _ISFirearmRadialMenu_onKeyReleased
})
------------------------------------------
-- ISMoveableInfoWindow
---- "Toggle Moveable Panel Mode"
------------------------------------------
local _ISMoveableInfoWindow_moveablePanelModeKey = ISMoveableInfoWindow.moveablePanelModeKey
Events.OnKeyPressed.Remove(_ISMoveableInfoWindow_moveablePanelModeKey)
keyman.addKeyEvents("Toggle Moveable Panel Mode", {
["OnKeyPressed"] = _ISMoveableInfoWindow_moveablePanelModeKey
})
------------------------------------------
-- ISEmoteRadialMenu
---- "Emote"
---- "Shout"
------------------------------------------
local _ISEmoteRadialMenu_onKeyReleased = ISEmoteRadialMenu.onKeyReleased
local _ISEmoteRadialMenu_onKeyPressed = ISEmoteRadialMenu.onKeyPressed
local _ISEmoteRadialMenu_onKeyRepeat = ISEmoteRadialMenu.onKeyRepeat
Events.OnKeyStartPressed.Remove(_ISEmoteRadialMenu_onKeyPressed)
Events.OnKeyKeepPressed.Remove(_ISEmoteRadialMenu_onKeyRepeat)
Events.OnKeyPressed.Remove(_ISEmoteRadialMenu_onKeyReleased)
keyman.addKeyEvents("Emote", {
["OnKeyStartPressed"] = _ISEmoteRadialMenu_onKeyPressed,
["OnKeyKeepPressed"] = _ISEmoteRadialMenu_onKeyRepeat,
["OnKeyPressed"] = _ISEmoteRadialMenu_onKeyReleased
})
keyman.addKeyEvents("Shout", {
["OnKeyStartPressed"] = _ISEmoteRadialMenu_onKeyPressed,
["OnKeyKeepPressed"] = _ISEmoteRadialMenu_onKeyRepeat,
["OnKeyPressed"] = _ISEmoteRadialMenu_onKeyReleased
})
------------------------------------------
-- ISSearchManager
---- "Toggle Search Mode"
------------------------------------------
local _ISSearchManager_handleKeyPressed = ISSearchManager.handleKeyPressed
Events.OnKeyPressed.Remove(_ISSearchManager_handleKeyPressed)
keyman.addKeyEvents("Toggle Search Mode", {
["OnKeyPressed"] = _ISSearchManager_handleKeyPressed
})
------------------------------------------
-- ISChat
---- "Toggle chat"
---- "Alt toggle chat"
------------------------------------------
local _ISChat_onToggleChatBox = ISChat.onToggleChatBox
--local _ISChat_onKeyKeepPressed = ISChat.onKeyKeepPressed
Events.OnKeyPressed.Remove(_ISChat_onToggleChatBox)
keyman.addKeyEvents("Toggle chat", {
["OnKeyPressed"] = _ISChat_onToggleChatBox
})
keyman.addKeyEvents("Alt toggle chat", {
["OnKeyPressed"] = _ISChat_onToggleChatBox
})
keyman.addKeyEvents("Switch chat stream", {
["OnKeyPressed"] = _ISChat_onToggleChatBox
})
------------------------------------------
-- Escape!
---- "Main Menu"
------------------------------------------
local _ToggleEscapeMenu = ToggleEscapeMenu
Events.OnKeyPressed.Remove(_ToggleEscapeMenu)
keyman.addKeyEvents("Main Menu", {
["OnKeyPressed"] = _ToggleEscapeMenu
})
------------------------------------------
-- SpeedControlsHandler
---- "Pause"
---- "Normal Speed"
---- "Fast Forward x1"
---- "Fast Forward x2"
---- "Fast Forward x3"
------------------------------------------
local _SpeedControlsHandler_onKeyPressed = SpeedControlsHandler.onKeyPressed
Events.OnKeyPressed.Remove(_SpeedControlsHandler_onKeyPressed)
if not isClient() then
keyman.addKeyEvents("Pause", {
["OnKeyPressed"] = _SpeedControlsHandler_onKeyPressed
})
keyman.addKeyEvents("Normal Speed", {
["OnKeyPressed"] = _SpeedControlsHandler_onKeyPressed
})
keyman.addKeyEvents("Fast Forward x1", {
["OnKeyPressed"] = _SpeedControlsHandler_onKeyPressed
})
keyman.addKeyEvents("Fast Forward x2", {
["OnKeyPressed"] = _SpeedControlsHandler_onKeyPressed
})
keyman.addKeyEvents("Fast Forward x3", {
["OnKeyPressed"] = _SpeedControlsHandler_onKeyPressed
})
end
------------------------------------------
-- SurvivalGuideManager
---- "Toggle Survival Guide"
------------------------------------------
local _SurvivalGuideManager_onKeyPressed = SurvivalGuideManager.onKeyPressed
Events.OnKeyPressed.Remove(_SurvivalGuideManager_onKeyPressed)
keyman.addKeyEvents("Toggle Survival Guide", {
["OnKeyPressed"] = _SurvivalGuideManager_onKeyPressed
})
end
return overrides

View File

@@ -0,0 +1,260 @@
local conf = require('BindAidConfig')
local mouse = {
curMouseX = 0,
curMouseY = 0,
prevMouseX = 0,
prevMouseY = 0,
hideDelta = 1,
hiding = false,
shouldHide = true,
hideDelay = 10, -- seconds
mouseButtons = 3,
buttonState = {},
buttonData = {}
}
mouse._onTickMouseHide = function()
-- Get our position
mouse.curMouseX = mouse.getX()
mouse.curMouseY = mouse.getY()
-- if our position is the same as last frame
---- Since we want this to work on everthing; we have to check this manually
if (mouse.curMouseX == mouse.prevMouseX and mouse.curMouseY == mouse.prevMouseY) then
-- if we are already hiding, do
if mouse.hiding then
mouse.setCursorVisible(false)
else
-- otherwise, increment
mouse.hideDelta = mouse.hideDelta + 1
-- We changed hideDelay on start to be in seconds
if mouse.hideDelta >= mouse.hideDelay then
mouse.hiding = true
end
end
else
-- Reset
mouse.hiding = false
mouse.hideDelta = 1
mouse.prevMouseX = mouse.curMouseX
mouse.prevMouseY = mouse.curMouseY
end
end
mouse.buttonEvents = {
["OnMouseMiddleDown"] = {},
["OnMouse4Down"] = {},
["OnMouse5Down"] = {},
["OnMouse6Down"] = {},
["OnMouse7Down"] = {},
["OnMouse8Down"] = {},
["OnMouse9Down"] = {},
["OnMouse10Down"] = {},
["OnMouse11Down"] = {},
["OnMouse12Down"] = {},
["OnMouseMiddleHold"] = {},
["OnMouse4Hold"] = {},
["OnMouse5Hold"] = {},
["OnMouse6Hold"] = {},
["OnMouse7Hold"] = {},
["OnMouse8Hold"] = {},
["OnMouse9Hold"] = {},
["OnMouse10Hold"] = {},
["OnMouse11Hold"] = {},
["OnMouse12Hold"] = {},
["OnMouseMiddleUp"] = {},
["OnMouse4Up"] = {},
["OnMouse5Up"] = {},
["OnMouse6Up"] = {},
["OnMouse7Up"] = {},
["OnMouse8Up"] = {},
["OnMouse9Up"] = {},
["OnMouse10Up"] = {},
["OnMouse11Up"] = {},
["OnMouse12Up"] = {},
}
local _mouseDownEvents = {
-- 0
false, --1
"OnMouseMiddleDown",
"OnMouse4Down",
"OnMouse5Down",
"OnMouse6Down",
"OnMouse7Down",
"OnMouse8Down",
"OnMouse9Down",
"OnMouse10Down",
"OnMouse11Down",
"OnMouse12Down"
}
local _mouseHoldEvents = {
-- 0
false, --1
"OnMouseMiddleHold",
"OnMouse4Hold",
"OnMouse5Hold",
"OnMouse6Hold",
"OnMouse7Hold",
"OnMouse8Hold",
"OnMouse9Hold",
"OnMouse10Hold",
"OnMouse11Hold",
"OnMouse12Hold"
}
local _mouseUpEvents = {
-- 0
false, --1
"OnMouseMiddleUp",
"OnMouse4Up",
"OnMouse5Up",
"OnMouse6Up",
"OnMouse7Up",
"OnMouse8Up",
"OnMouse9Up",
"OnMouse10Up",
"OnMouse11Up",
"OnMouse12Up"
}
local addTo = function(key, data, input)
local o = data[key] or {}
o[#o+1] = input
data[key] = o
return data
end
mouse.Add = function(data)
for i,v in pairs(data) do
mouse.buttonEvents = addTo(i, mouse.buttonEvents, v)
end
end
local _event -- cache
local mouseEvent = function(button, go)
_event = mouse.buttonData[button]
if not _event then return end
if _event.press then
if go then
_event = mouse.buttonEvents[_mouseHoldEvents[button]]
for i=1, #_event do
_event[i](mouse.getX(), mouse.getY())
end
else
_event.press = false
_event = mouse.buttonEvents[_mouseUpEvents[button]]
for i=1, #_event do
_event[i](mouse.getX(), mouse.getY())
end
end
else
if go then
_event.press = true
_event = mouse.buttonEvents[_mouseDownEvents[button]]
for i=1, #_event do
_event[i](mouse.getX(), mouse.getY())
end
end
end
end
local mouseEmulate = function(button, go)
_event = mouse.buttonData[button]
if not _event then return end
if _event.press then
if go then
-- hold
triggerEvent("OnKeyKeepPressed", _event.key)
else
-- up
triggerEvent("OnKeyPressed", _event.key)
_event.press = false
end
else
if go then
-- down
triggerEvent("OnKeyStartPressed", _event.key)
_event.press = true
end
end
end
local mouseBoth = function(button, go)
_event = mouse.buttonData[button]
if not _event then return end
if _event.press then
if go then
-- hold
triggerEvent("OnKeyKeepPressed", _event.key)
_event = mouse.buttonEvents[_mouseHoldEvents[button]]
for i=1, #_event do
_event[i](mouse.getX(), mouse.getY())
end
else
-- up
triggerEvent("OnKeyPressed", _event.key)
_event.press = false
_event = mouse.buttonEvents[_mouseUpEvents[button]]
for i=1, #_event do
_event[i](mouse.getX(), mouse.getY())
end
end
else
if go then
-- down
triggerEvent("OnKeyStartPressed", _event.key)
_event.press = true
_event = mouse.buttonEvents[_mouseDownEvents[button]]
for i=1, #_event do
_event[i](mouse.getX(), mouse.getY())
end
end
end
end
mouse.buildButtonData = function()
mouse.buttonData = {}
local buttons = { false, } -- 1
for _,v in ipairs(keyBinding) do
if luautils.stringStarts(v.value, "Bindaid_MouseKey") then
buttons[#buttons+1] = getCore():getKey(v.value)
end
end
for i=2, mouse.mouseButtons do
local key = (buttons[i] and buttons[i] ~= 0 and buttons[i]) or nil
mouse.buttonData[i] = {
func = (key and conf.Conf.emulateAndEvent and mouseBoth) or (key and mouseEmulate) or mouseEvent,
key = key
}
end
end
mouse._onClickButtonHandler = function()
for i=2, mouse.mouseButtons do
mouse.buttonData[i].func(i, mouse.isButtonDown(i))
end
end
mouse._onStart = function()
mouse.buildButtonData()
if conf.Local.mouseButtonSupport then
Events.OnTick.Add(mouse._onClickButtonHandler)
end
end
mouse._onBoot = function()
-- Since these are static functions, let's cache them
mouse.getX = Mouse.getX
mouse.getY = Mouse.getY
mouse.setCursorVisible = Mouse.setCursorVisible
mouse.isButtonDown = Mouse.isButtonDown
mouse.isButtonDownUICheck = Mouse.isButtonDownUICheck
end
return mouse

View File

@@ -0,0 +1,25 @@
-- These fixes are included in other mods, so this prevents from multiple runnings
if not Exterminator then
Exterminator = {}
end
if not Exterminator.onEnterFromGame then
-- Protects Against a Known Options Bug
-- Thanks Burryaga!
Exterminator.onEnterFromGame = MainScreen.onEnterFromGame
function MainScreen:onEnterFromGame()
Exterminator.onEnterFromGame(self)
-- Guarantee that when you ENTER the options menu, the game does not think you've already changed your damn options.
MainOptions.instance.gameOptions.changed = false
end
end
if not Exterminator.MainOptions_apply then
-- Adds an event when Game Options are changed
LuaEventManager.AddEvent("OnSettingsApply")
Exterminator.MainOptions_apply = MainOptions.apply
function MainOptions:apply(closeAfter)
Exterminator.MainOptions_apply(self, closeAfter)
triggerEvent("OnSettingsApply")
end
end

View File

@@ -0,0 +1,83 @@
------------------------------------------
-- BindAid Overrides -- Server
--- All of the override functions BindAid Supports
------------------------------------------
if not (isClient() or not isServer()) then return end
local keyman = require('KeybindManager')
local conf = require("BindAidConfig")
local overrides = {}
-- We want the LAST references to all of this
overrides.onStart = function()
-- Don't do this if disabled
if not conf.Local.keyinputOptimize then return end
------------------------------------------
-- ItemBindingHandler
---- "Equip/Turn On/Off Light Source"
---- "ToggleVehicleHeadlights"
------- This one is not actually an Event, but is called by ISLightSourceRadialMenu
------- This is here to catch the headlights mostly
------------------------------------------
-- local _ItemBindingHandler_onKeyPressed = ItemBindingHandler.onKeyPressed
-- ItemBindingHandler.onKeyPressed = function(key)
-- if not keyman.isModKeyDown() then
-- _ItemBindingHandler_onKeyPressed(key)
-- end
-- end
------------------------------------------
-- xpUpdate
---- "Toggle Skill Panel"
---- "Toggle Health Panel"
---- "Toggle Info Panel"
---- "Toggle Clothing Protection Panel"
------------------------------------------
local _xpUpdate_displayCharacterInfo = xpUpdate.displayCharacterInfo
Events.OnKeyPressed.Remove(_xpUpdate_displayCharacterInfo)
keyman.addKeyEvents("Toggle Skill Panel", {
["OnKeyPressed"] = _xpUpdate_displayCharacterInfo
})
keyman.addKeyEvents("Toggle Health Panel", {
["OnKeyPressed"] = _xpUpdate_displayCharacterInfo
})
keyman.addKeyEvents("Toggle Info Panel", {
["OnKeyPressed"] = _xpUpdate_displayCharacterInfo
})
keyman.addKeyEvents("Toggle Clothing Protection Panel", {
["OnKeyPressed"] = _xpUpdate_displayCharacterInfo
})
------------------------------------------
-- ISMoveableCursor
---- "Run"
---- "Interact"
---- "Toggle mode"
------------------------------------------
local _ISMoveableCursor_exitCursorKey = ISMoveableCursor.exitCursorKey
local _ISMoveableCursor_changeModeKey = ISMoveableCursor.changeModeKey
Events.OnKeyPressed.Remove(_ISMoveableCursor_exitCursorKey)
Events.OnKeyKeepPressed.Remove(_ISMoveableCursor_exitCursorKey)
keyman.addKeyEvents("Run", {
["OnKeyPressed"] = _ISMoveableCursor_exitCursorKey,
["OnKeyKeepPressed"] = _ISMoveableCursor_exitCursorKey,
})
keyman.addKeyEvents("Interact", {
["OnKeyPressed"] = _ISMoveableCursor_exitCursorKey,
["OnKeyKeepPressed"] = _ISMoveableCursor_exitCursorKey,
})
Events.OnKeyPressed.Remove(_ISMoveableCursor_changeModeKey)
keyman.addKeyEvents("Toggle mode", {
["OnKeyPressed"] = _ISMoveableCursor_changeModeKey
})
end
return overrides

View File

@@ -0,0 +1,84 @@
-- Only load in Single Player or if a Multiplayer client
if not (isClient() or not isServer()) then return end
local conf = require('BindAidConfig')
local keyman = require('KeybindManager')
local mouse -- client, loaded onBoot
local localOverrides -- client, loaded onStart
local serverOverrides -- server, loaded onStart
local autoHideTimes = {
1,2,5,10,30,60
}
local onBoot = function()
mouse = require('Mouse')
-- Added to Sync the options
conf.OnApplyOptions = function()
mouse.hideDelay = (autoHideTimes[conf.Local.autohideMouseTime] or 5) * getPerformance():getFramerate()
mouse.mouseButtons = conf.Local.mouseButtonCount + 1
Events.OnTickEvenPaused.Remove(mouse._onTickMouseHide)
Events.OnFETick.Remove(mouse._onTickMouseHide)
if conf.Local.autohideMouse then
-- Do the game and the Pause
Events.OnTickEvenPaused.Add(mouse._onTickMouseHide)
-- Do the Main Menu
Events.OnFETick.Add(mouse._onTickMouseHide)
end
end
conf._onBoot()
mouse._onBoot()
keyman._onBoot()
keyman.addKeybind("[Bindaid]", 'Bindaid_Modkey', Keyboard.KEY_LCONTROL)
if conf.Local.mouseButtonSupport then
for i=2, mouse.mouseButtons do
keyman.addKeybind("[Bindaid]", 'Bindaid_MouseKey_' .. tostring(i), 0)
end
end
end
local onSettings = function()
if conf.Local.keyinputOptimize then
keyman.buildKeymap()
end
if conf.Local.mouseButtonSupport then
mouse.buildButtonData()
end
end
-- The postStart is the last OnGameStart function that is called
---- Will run all of the other `starts` for keys to get the latest key events
local postStart = function()
localOverrides.onStart()
if serverOverrides then serverOverrides.onStart() end
keyman._onStart()
mouse._onStart()
end
local onStart = function()
localOverrides = require('ClientKeys')
serverOverrides = require('ServerKeys')
Events.OnSettingsApply.Add(onSettings)
-- Add the postStart function to the OnGameStart stack now
---- This ENSURES that it is last. :)
Events.OnGameStart.Add(postStart)
end
local function BindAid()
print(getText("UI_Init_Bindaid"))
Events.OnGameBoot.Add(onBoot)
Events.OnGameStart.Add(onStart)
end
BindAid()

View File

@@ -0,0 +1,309 @@
-- Configuration Manager
---- Made by dhert, use this however you want. :)
-- NOTE: GIVE THIS FILE A UNIQUE SANDNAME IN YOUR MOD TO NOT OVERWRITE OTHER USES OF THIS SCRIPT!
---------------------
-- Edit the "options_data" array for your settings using Mod Options syntax.
--- Comment out the OPTIONS table in full to disable ModOptions support
--- Do not change the OPTIONS, options_data, mod_id, mod_shortname, and mod_fullname variable names, but fill them in with your own values
--- The options OnApplyMainMenu and OnApplyInGame will be added/overwritten later, do not use them.
local OPTIONS = {
options_data = {
autohideMouse = {
name = "UI_ModOptions_Bindaid_autohideMouse",
default = true,
},
autohideMouseTime = {
"1", "2", "5", "10", "30", "60",
name = "UI_ModOptions_Bindaid_autohideMouseTime",
default = 4,
},
mouseButtonSupport = {
name = "UI_ModOptions_Bindaid_mouseButtonSupport",
default = true,
},
mouseButtonCount = {
"1", "2", "3", "4", "5", "6", "7", "8", "9", "10",
name = "UI_ModOptions_Bindaid_mouseButtonCount",
default = 3
},
emulateAndEvent = {
name = "UI_ModOptions_Bindaid_emulateAndEvent",
default = false,
},
keyinputOptimize = {
name = "UI_ModOptions_Bindaid_keyinputOptimize",
default = true,
},
},
mod_id = "BindAid",
mod_shortname = "BindAid",
mod_fullname = getText("UI_optionscreen_binding_Bindaid")
}
-- Controls whether ModOptions will overwrite Sandbox Options with the same name by default.
--- To allow Server Owners to control this, keep this as "TRUE" add the IgnoreLocalOptions option to your Sandbox Namespace (see above)
local OVERWRITE = true
-- Set the name of your SandboxVariable Namespace.
--- The "Namespace" is the group of Sandbox Variables for your mod: "SanboxVars.Namespace.Variable"
--- You build Sanbox Variables as: "option Namespace.Variable"
---- If you have only one Namespace:
----- local SANDNAME = "Namespace"
---- If you have more than one, then build a table:
----- local SANDNAME = {"One", "Two", "etc"}
----- Comment out to disable Sandbox Support
--local SANDNAME = "Bindaid"
---------------------
-- Additional Documentation
---------------------
-- This module can be used as a cache for your Sandbox Options and ModOptions
--- Simply modify the variables above to setup your mod's Config cache.
--- This can be placed anywhere; client, server, or shared
--- Include this in your mod and use as:
---- local config = require('YourConfigManagerFileNameName') -- no .lua at the end
---- Can use this as:
----- if config.Get("Option") then
----- -- do something
----- end
----
---- You also have access to the raw data using:
---- config.Conf["Option"] OR config.Conf.Option
---
--- Defined ModOptions are made available in the config.Conf table.
---- ModOptions that have the same name as a Sandbox options will overwrite.
----- In your Sandbox namespace, add the option 'IgnoreLocalOptions' to allow server owners to force sync options
----- Example:
------ option Namespace.IgnoreLocalOptions
------ {
------ type = boolean, default = false,
------ page = Namespace,
------ translation = Namespace_IgnoreLocalOptions,
------ }
----- Alternatively, set the OVERWRITE option to false to only sync ModOptions and not overwrite Sandbox options
---------------------
----------- Don't edit below here, unless you know what you're doing. :)
---------------------
local Conf = {
-- This is our main object for configuration; read from this.
Conf = {},
-- This is the user's configuration, only defined if Mod Options exists. Used internally, not intended to be used but left here; already synced with Conf.
Local = nil,
-- A function that can be defined to run when Sandbox Settings are read
OnSandboxConfig = nil,
-- A function that can be defined to run when settings are applied. Called ANY time that Mod Options are changed
OnApplyOptions = nil,
-- A function that can be defined to run when settings are applied in game
OnApplyOptionsInGame = nil
}
-- Print the available Configuration
--- Useful for debugging
Conf.Print = function()
for i,v in pairs(Conf.Conf) do
print(" " .. tostring(i) .. " = " .. tostring(v))
end
end
-- Sync Sandbox Configuration from given Namespace
local syncSandboxConfig = function(nmspce)
local vars = SandboxVars[nmspce]
if not vars then
print("Unable to find namespace: " .. nmspce)
return false
end
for i,v in pairs(vars) do
Conf.Conf[i] = v
end
end
-- Sync Sandbox Configuration from Multiple Namespaces
local syncSandboxConfigMulti = function(nmspces)
for _,n in ipairs(nmspces) do
syncSandboxConfig(n)
end
end
-- Syncs Sandbox and available ModOptions
Conf.SyncConfig = function()
-- Reset our conf object
Conf.Conf = {}
if SANDNAME then
if type(SANDNAME) == "string" then
syncSandboxConfig(SANDNAME)
else
syncSandboxConfigMulti(SANDNAME)
end
end
if not Conf.Conf.IgnoreLocalOptions and Conf.Local and OVERWRITE then
-- Sync our Local configuration into our Conf data, overriding any Sandbox Options
for i,v in pairs(Conf.Local) do
Conf.Conf[i] = v
end
elseif Conf.Local then
-- only sync options that do not exist
for i, v in pairs(Conf.Local) do
if Conf.Conf[i] == nil then
Conf.Conf[i] = v
end
end
end
end
-- Get configuration value from given `key`
-- Returns default, nil if no default given
--- @param key string
--- @param default number|boolean|string -- optional
--- @return number|boolean|string|nil
Conf.Get = function(key, default)
return (Conf.Conf[key] == nil and default) or Conf.Conf[key]
end
-- Apply Mod Options in general, run the OnApplyOptions function if available
---- ALWAYS called when ModOptions are changed
local function applyModOptions(data)
Conf.Local = {}
for i,v in pairs(data.settings.options) do
Conf.Local[i] = v
end
if Conf.OnApplyOptions then
Conf.OnApplyOptions(Conf.Local)
end
end
-- Apply Mod Options in game, run the OnApplyOptionsInGame function if available
local function applyModOptionsGame(data)
applyModOptions(data)
Conf.SyncConfig()
if Conf.OnApplyOptionsInGame then
Conf.OnApplyOptionsInGame(Conf.Conf)
end
end
-- Function that handles ModOptions
local modOptions = function()
-- Add our apply event functions to the settings
for i,_ in pairs(OPTIONS.options_data) do
if not OPTIONS.options_data[i].tooltip then
local name = OPTIONS.options_data[i].name
if name then
name = name .. "_tooltip"
local tt = getText(name)
if tt ~= name then -- getText returns the same string if no translation exists, so just check for that
OPTIONS.options_data[i].tooltip = tt
end
end
end
OPTIONS.options_data[i].OnApplyMainMenu = applyModOptions
OPTIONS.options_data[i].OnApplyInGame = applyModOptionsGame
end
-- Load our stuff
ModOptions:getInstance(OPTIONS)
ModOptions:loadFile()
applyModOptions({settings = OPTIONS})
-- Build our Configuration
Events.OnInitGlobalModData.Add(function()
applyModOptionsGame({settings = OPTIONS})
end)
end
-- Add support for loading the ModOptions config file, even if ModOptions is not currently active
local forceModOptionsConfig = function()
-- Set our defaults
local config = {}
for i,v in pairs(OPTIONS.options_data) do
config[i] = v.default
end
-- Read and apply configuration from the local ini file
local function Load()
local file = getFileReader("mods_options.ini", false)
local line = ""
local found = false
if file then
while true do
line = file:readLine()
if not line then
break
end
line = line:trim()
if line ~= "" then
local next = false
local k = line:match('^%[([^%[%]]+)%]$')
if k and k == OPTIONS.mod_id then
found = true
next = true
elseif k and k ~= OPTIONS.mod_id then
if found then break end -- we found the next one, so we're done
end
if found and not next then
local i, v = line:match('^([%w|_]+)%s-=%s-(.+)$')
if(i and v)then
v = v:trim()
i = i:trim()
if(tonumber(v))then
v = tonumber(v)
elseif(v:lower() == 'true')then
v = true
elseif(v:lower() == 'false')then
v = false
else -- we only want the above
v = nil
end
config = config or {}
-- if we failed to read from config, then keep the default
config[i] = (v == nil and config[i]) or v
end
end
end
end
file:close()
end
end
Load()
if isDebugEnabled() then
print("Force Loading Mod Options")
for i,v in pairs(config) do
print(" Loaded: " .. i .. " = " .. tostring(v))
end
end
-- add condition to not do this?
Conf.Local = config
Conf.SyncConfig()
if Conf.OnApplyOptions then
Conf.OnApplyOptions(Conf.Local)
end
end
Conf._onBoot = function()
-- Runs on the local client, or in singleplayer
if isClient() or not isServer() then
-- Add ModOptions if installed, sync Sandbox and ModOptions
if OPTIONS and ModOptions and ModOptions.getInstance then
modOptions()
else
-- No ModOptions? That's fine, just sync our defined Mod Options and Sandbox options anyways
Events.OnInitGlobalModData.Add(forceModOptionsConfig)
end
else
-- If its a server, just sync our Sandbox config
Events.OnInitGlobalModData.Add(Conf.SyncConfig)
end
end
return Conf

View File

@@ -0,0 +1,234 @@
-- Only load in Single Player or if a Multiplayer client
if not (isClient() or not isServer()) then return end
-- Storage module for defining keybinds used in the game.
local keyman = {
-- -- keys = {
-- [key] = {
-- ["OnKeyPressed"] = {}
-- ["OnKeyStartPressed"] = {},
-- },
-- }
keys = {},
-- Functions, indexed by their keybinds
funcs = {},
addKeys = {}
}
local conf
local addTo = function(key, data, input)
local o = data[key] or {}
o[#o+1] = input
data[key] = o
return data
end
-- Add a function to the map by event
--- @param keyName string
--- @param events table
---- events should be a table of functions, indexed by event | { ["event"]=function, ["event"]=function }
---- valid events are "OnKeyStartPressed", "OnModKeyStartPressed", "OnKeyKeepPressed", "OnModKeyKeepPressed", "OnKeyPressed", "OnModKeyPressed"
keyman.addKeyEvents = function(keyName, events)
keyman.funcs = addTo(keyName, keyman.funcs, events)
end
-- Add a function to the map by event
--- @param section string
--- @param keyName string
--- @param default integer
--- @param events table
---- events should be a table of functions, indexed by event | { ["event"]=function, ["event"]=function }
---- valid events are "OnKeyStartPressed", "OnModKeyStartPressed", "OnKeyKeepPressed", "OnModKeyKeepPressed", "OnKeyPressed", "OnModKeyPressed"
keyman.addKeybind = function(section, keyName, default, events)
keyman.addKeys = addTo(section, keyman.addKeys, {value=keyName, key=default or 0})
if events then
keyman.addKeyEvents(keyName, events)
end
end
local isMKdown = false
-- Returns if the Modkey is down
--- @return boolean
keyman.isModkeyDown = function()
return isMKdown
end
-- Prints keys in the `funcs` table, pre-cache
--- Used for Debug
keyman.PrintFuncs = function()
print("Functions: ")
for i,v in pairs(keyman.funcs) do
print("Key: " .. i)
for j,k in ipairs(v) do
print(" Index: " .. tostring(j))
for name,func in pairs(k) do
print(" Has Event: " .. name .. " | Function: " .. tostring(func))
end
end
end
end
-- Prints keys in the `keys` table, the cache
--- Used for Debug, only available in game
keyman.PrintKeys = function()
print("Keys: ")
for i,v in pairs(keyman.keys) do
print("Key: " .. i)
for j,event in pairs(v) do
print(" Event: " .. tostring(j) .. " | Total: " .. tostring(event and #event))
for index,func in ipairs(event) do
print(" Index: " .. index .. " | Function: " .. tostring(func))
end
end
end
end
local handleInput = function(key, mevent, event)
local temp = keyman.keys[key] and (keyman.keys[key][mevent] or keyman.keys[key][event])
if not temp then return end
for i=1, #temp do
temp[i](key)
end
end
local _onKeyPressed = function(key)
handleInput(key, (isMKdown and "OnModKeyPressed"), "OnKeyPressed")
end
local _onKeyStartPressed = function(key)
handleInput(key, (isMKdown and "OnModKeyStartPressed"), "OnKeyStartPressed")
end
local _onKeyKeepPressed = function(key)
handleInput(key, (isMKdown and "OnModKeyKeepPressed"), "OnKeyKeepPressed")
end
local _onCustomUIKey = function(key)
handleInput(key, (isMKdown and "OnCustomUIModKey"), "OnCustomUIKey")
end
-- Check if our given function has been already added to the current key/event
---- this could be better, but most of the time this should be a relatively small check
local checkIfExists = function(obj, data)
if not obj then return false end
for _,v in ipairs(obj) do
if v == data then
return true
end
end
return false
end
keyman.buildKeymap = function()
keyman.keys = {}
for i,v in pairs(keyman.funcs) do
-- get the key for the bind we have stored
local key = getCore():getKey(i)
-- check if we didn't get a key for some reason...
if key then
-- get the index for this key in our object
local obj = keyman.keys[key] or {}
for _,k in ipairs(v) do
for event, func in pairs(k) do
if not checkIfExists(obj[event], func) then
obj = addTo(event, obj, func)
end
end
end
-- reassign
keyman.keys[key] = obj
end
end
--keyman.PrintKeys()
end
local _isKeyDown = isKeyDown
local function modkey(key)
isMKdown = _isKeyDown(key)
end
-- this is for when we disable the event, need to prevent those modkeys from being added; they're not events
local modkeyevents = {
["OnModKeyPressed"] = true,
["OnModKeyStartPressed"] = true,
["OnModKeyKeepPressed"] = true,
["OnCustomUIModKey"] = true,
}
-- Build our Keymap and setup our events
keyman._onStart = function()
keyman.addKeyEvents('Bindaid_Modkey', {
["OnKeyStartPressed"] = modkey,
["OnKeyPressed"] = modkey,
})
if conf.Local.keyinputOptimize then
keyman.buildKeymap()
Events.OnKeyPressed.Add(_onKeyPressed)
Events.OnKeyStartPressed.Add(_onKeyStartPressed)
Events.OnKeyKeepPressed.Add(_onKeyKeepPressed)
Events.OnCustomUIKey.Add(_onCustomUIKey)
else
-- If the keyinput optimizer is disabled, this will only contain functions added by other mods
---- Add those keys to the event stack
for _,v in pairs(keyman.funcs) do
for _,k in ipairs(v) do
for event, func in pairs(k) do
-- skip modkey events
if not modkeyevents[event] then
Events[event].Add(func)
end
end
end
end
end
end
local postBoot = function()
conf = require('BindAidConfig')
local keys = {}
local headers = {}
local index
-- index the current keyBinding variable
for _,v in ipairs(keyBinding) do
if luautils.stringStarts(v.value, "[") then
index = v.value
keys[index] = {}
headers[#headers+1] = v.value
else
keys[index][#keys[index]+1] = v
end
end
-- Add our functions to the appropriate section
for section,binds in pairs(keyman.addKeys) do
--print("Section: " .. tostring(section))
if not keys[section] then
headers[#headers+1] = section
keys[section] = {}
end
for _,k in ipairs(binds) do
--print(" Key: " .. tostring(k.value) .. " = " .. tostring(k.key))
keys[section][#keys[section]+1] = k
end
end
-- re-add our bindings back into the keyBinding variable
keyBinding = {}
for _,v in ipairs(headers) do
--print("Index: " .. tostring(i) .. " | Header: " .. v)
keyBinding[#keyBinding+1] = {value = v, key = nil} -- header
for _,k in ipairs(keys[v]) do
--print(" Value: " .. k.value .. " | Key: " .. k.key)
keyBinding[#keyBinding+1] = {value = k.value, key = k.key} -- let's make a deep copy
end
end
end
keyman._onBoot = function()
Events.OnGameBoot.Add(postBoot)
end
return keyman

View File

@@ -0,0 +1,34 @@
UI_EN = {
UI_Init_Bindaid = "Hello: BindAid!",
UI_optionscreen_binding_Bindaid = "BindAid",
UI_optionscreen_binding_Bindaid_Modkey = "Modifier Key",
UI_optionscreen_binding_Bindaid_MouseKey_2 = "Middle Mouse Button",
UI_optionscreen_binding_Bindaid_MouseKey_3 = "Mouse Button 3",
UI_optionscreen_binding_Bindaid_MouseKey_4 = "Mouse Button 4",
UI_optionscreen_binding_Bindaid_MouseKey_5 = "Mouse Button 5",
UI_optionscreen_binding_Bindaid_MouseKey_6 = "Mouse Button 6",
UI_optionscreen_binding_Bindaid_MouseKey_7 = "Mouse Button 7",
UI_optionscreen_binding_Bindaid_MouseKey_8 = "Mouse Button 8",
UI_optionscreen_binding_Bindaid_MouseKey_9 = "Mouse Button 9",
UI_optionscreen_binding_Bindaid_MouseKey_10 ="Mouse Button 10",
UI_optionscreen_binding_Bindaid_MouseKey_11 ="Mouse Button 11",
UI_optionscreen_binding_Bindaid_MouseKey_12 ="Mouse Button 12",
UI_ModOptions_Bindaid_autohideMouse = "AutoHide Mouse",
UI_ModOptions_Bindaid_autohideMouse_tooltip = "Hide the mouse after it has not been moved.",
UI_ModOptions_Bindaid_autohideMouseTime = "AutoHide Mouse Delay",
UI_ModOptions_Bindaid_UI_ModOptions_Bindaid_autohideMouseTime_tooltip = "AutoHide the Mouse after this many Seconds",
UI_ModOptions_Bindaid_mouseButtonSupport = "Enable Additional Mouse Button Support",
UI_ModOptions_Bindaid_mouseButtonSupport_tooltip = "NOTE: You MUST restart the game if this is changed.",
UI_ModOptions_Bindaid_mouseButtonCount = "Additional Mouse Buttons",
UI_ModOptions_Bindaid_mouseButtonCount_tooltip = "Number of additional Mouse Buttons to support. <LINE> NOTE: You MUST restart the game if this is changed.",
UI_ModOptions_Bindaid_emulateAndEvent = "Run Mouse and Emulated Events",
UI_ModOptions_Bindaid_emulateAndEvent_tooltip = "When a Mouse Button is pressed, run both the Mouse Event functions and the Emulated Key if set. If disabled, only the assigned Emulated Key is performed. If no Emulated Key, then Mouse Events are used.",
UI_ModOptions_Bindaid_keyinputOptimize = "Enable Keyboard Input Optimization",
UI_ModOptions_Bindaid_keyinputOptimize_tooltip = "Enables the Keyboard Input Optimization, please read the Mod's Description for full information on this. <LINE> If you receive any issues with input, please disable this. <LINE> NOTE: You MUST restart the game if this is changed while in-game.",
}

View File

@@ -0,0 +1,11 @@
name=BindAid
id=BindAid
authors=dhert
description=
description=Optimizes keyboard input, adds native mousebutton emulation, and more!
pzversion=41
tags=Interface;Framework;Misc
poster=poster.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 KiB

353
BindAid/README.md Normal file
View File

@@ -0,0 +1,353 @@
Supports B41+ and Multiplayer.
A new save is not required.
# BindAid
Input Binding Manager for Project Zomboid which provides optimizations for keyboard input events and additional Mouse button support with optional keybaord emulation.
# Features
- Keyboard Input Optimization
- Modkey Support
- Additional Mouse Button Support
- Mouse Button Keyboard Emulation
- Autohide the Mouse Cursor
- Easy API for Modders!
## Mod Options
Mod Options is **REQUIRED** to configure this mod; however, it is not a requirement for its operation. When your first install this mod, please take some time to set up its configuration based on your own needs using Mod Options. Further information on the available options is available below.
When Mod Options is **NOT** enabled for a save, your configuration will still be read and applied. **THIS MEANS** Server Owners can safely include this mod without also requiring Mod Options, and user configuration will still be used.
## Keyboard Input Optimization
A full breakdown is available here: BindAid's Keyboard Input Optimization - What It Be, and How It Do
In summary, there are 4 general events that are triggered for input handling, and each event will call multiple functions for each key press.
| Event | Trigger | # of Functions |
| ----------- | ----------- | ----------- |
| OnKeyStartPressed | When a key is pressed | 10 |
| OnKeyKeepPressed | When a key is held, every frame | 9 |
| OnKeyPressed | When a key is released | 26 |
| OnCustomUIKey | When a key is released | 1 |
This occurs for **ALL** key presses, whether they have actions bound or not. Multiple key presses multiply the number of functions called per frame. This is not an issue for most people, but I have organized and optimized how this is handled so only 1 function is called during the event, which then calls the key press if applicable.
This feature can be disabled in the Mod Options configuration. If changed while in-game, a restart is required.
### Compatibility
I have created this in a way that should have maximum compatibility. If you have any issues with a vanilla key not being triggered, I would recommend disabling this optimization and letting me know. I cannot fix all mod compatibility, and some may require changes by the Modders. For Modders, please see: BindAid - Modder's Resource
- Mods that add their own **NEW** keys are not affected.
- Mods that modify vanilla functions for key presses will be compatible as long as their changes are made no later than OnGameStart, and any modifications are forwarded.
## Mouse Buttons
That's right, BindAid provides support for additional Mouse Buttons! Currently supports up to 10 additional Mouse Buttons, configurable in the Mod Options!
You will need to select the number of Mouse Buttons that you would like to have support for in the Mod Options. A restart of the game is required after you have made this change.
The Mouse Buttons have 2 different modes: Mouse Button Events (default) and Keyboard Emulation. An option is available to enable both methods to run as well.
### Mouse Button Events
When Mouse Button Events are in use, the additional Mouse Buttons are handled similarly to key bind events. Modders can hook into these events to allow additional actions to be done on a Mouse Button Press/Hold/Release. Please see: BindAid - Modder's Resource
**THIS MEANS, BY DEFAULT, MOUSE BUTTONS PERFORM NO NEW ACTIONS! MODDERS MUST ADD SUPPORT FOR THIS!**
### Keyboard Emulation
New in the "Keybinds" Options Tab will be the Mouse Buttons that are supported under the "BindAid" section, each set to a key bind of "None". Setting a Key here on a Mouse Button will instead cause an emulated key press to be done instead of the Mouse Event! For example, you can bind "Reload" to the Middle Mouse Button.
**NOTE!!!** The Emulated key press is ONLY done in Lua, so it will not trigger functions defined in Java. This means you cannot assign a key to "Move Forward" or something like that; it won't work. Actions such as Reloading, Shout/Emote, and many others are available, as well as new key binds added by other mods (the main target).
# BindAid's Keyboard Input Optimization - What It Be, and How It Do
There are 4 general events that are triggered for input handling, and each event will call multiple functions for each key press.
| Event | Trigger | # of Functions |
| ----------- | ----------- | ----------- |
| OnKeyStartPressed | When a key is pressed | 10 |
| OnKeyKeepPressed | When a key is held, every frame | 9 |
| OnKeyPressed | When a key is released | 26 |
| OnCustomUIKey | When a key is released | 1 |
This occurs for **ALL** key presses, whether they have actions bound or not. Multiple key presses multiply the number of functions called per frame.
Most of the time, this has minimal impact on performance for keyboard input in Project Zomboid as most of the functions called will immediately check for their key first and end. However, there are some that do not and may also perform additional checks; this is redundant when there is no action to perform on a key press and unnecessary overhead. On low-end machines, or when the game/device is undergoing stress, this can can cause be brief input delays.
Instead, the key binds, events, and functions that are being called have been organized into an array with the key and event as its index. All vanilla events are then removed from the event stack, and a single event is added for each to trigger the appropriate key and function per event given. This reduces the overhead for each of the Events to only call ONE function, and if the key is not found in the array then no further action is done.
## How it works
The key bind/event cache is built last during the `OnGameStart` event, so any other mods should have already made their changes at this point. I also have not experienced any mods that change key binds after `OnGameStart`, and am not sure why you would do this anyway. The cache is rebuilt when settings are updated.
The cache is built in the following manner:
```lua
cache[key] = {
["OnKeyStartPressed"] = { func1, func2 },
["OnKeyKeepPressed"] = { func1 }
}
```
The `key` is the numerical value of all key binds. When a key is pressed it is used to access the list of events, if any, bound to the key. The event then contains an indexed array of functions to run on this key press.
```lua
local handleInput = function(key, event)
local temp = cache[key] and cache[key][event]
if not temp then return end
for i=1, #temp do
temp[i](key)
end
end
```
## Why do this?
Most games engines I have found handles key presses in this way: where operations are indexed or there is some sort of additional control to prevent all functions from being run like this. While this may provide limited improvements, it is an improvement nonetheless.
## Benchmark
NOTE: This requires a change to the Lua in the base game by adding a new file, and may produce a lot of noise in your log. I don't recommend doing this unless you're curious, and if you do, you should be sure to delete this file or verify your game's files with Steam.
### Disclaimer
To be completely honest, we're not looking at substantial improvements for most players. The Event functions are already optimized, in that most simply end when the key doesn't match. In my testing, the current key bind input event stack takes on average 1 Millisecond (Ms) to complete; any delays come from the action being performed by the key and not from the number of functions being run. When there was a lot onscreen, and in a dedicated server, I could receive occasional delays of up to about 10Ms.
With this mod enabled, I consistently receive no more than 1Ms for each Event loop; unless an action is performed on the key, such as opening the crafting window.
Let's take the following code as our benchmark. In the base game folder, create the following file: `media/lua/shared/!a_KeyTimer.lua`
```lua
-- control for time.
--- this is reset on each call of the keystack for each key
local time = 0
-- event start functions, store the times
local onKeyDownStart = function(key)
time = getTimestampMs()
end
local onKeyHoldStart = function(key)
time = getTimestampMs()
end
local onKeyUpStart = function(key)
time = getTimestampMs()
end
-- Add our Events to the stack.
--- Due to how Project Zomboid loads files, this will be first on the Lua-side
Events.OnKeyStartPressed.Add(onKeyDownStart)
Events.OnKeyKeepPressed.Add(onKeyHoldStart)
Events.OnKeyPressed.Add(onKeyUpStart)
-- event end functions. subtract the current from the start, and print if greater than 1
local onKeyDownEnd = function(key)
time = getTimestampMs() - time
if time > 1 then
print("onKeyDownEnd: " .. tostring(key) .. " | Time: " .. tostring(time))
end
end
local onKeyHoldEnd = function(key)
time = getTimestampMs() - time
if time > 1 then
print("onKeyHoldEnd: " .. tostring(key) .. " | Time: " .. tostring(time))
end
end
local onKeyUpEnd = function(key)
time = getTimestampMs() - time
if time > 1 then
print("onKeyUpEnd: " .. tostring(key) .. " | Time: " .. tostring(time))
end
end
-- Add our key events when creating the player; most key events should be assigned by now.
local postStart = function()
Events.OnKeyStartPressed.Add(onKeyDownEnd)
Events.OnKeyKeepPressed.Add(onKeyHoldEnd)
Events.OnKeyPressed.Add(onKeyUpEnd)
end
Events.OnCreatePlayer.Add(postStart)
```
This will save the current Millisecond timestamp in the `time` variable at the start of each event. The `OnCreatePlayer` function adds our other functions to the end of the Event stacks, and so at the end of the Event the current Millisecond timestamp is subtracted from the start's to give us our delta time. If this takes over 1Ms, then it will print the key and the delta time.
This most commonly will begin to give results after there are a bunch of zombies on screen, and mostly during `onKeyUpEnd()` due to the number of functions called.
As mentioned previously, I always get good performance on my machine. I would love to hear any reports (positive or negative) if you tried this yourself!
# BindAid - Modder's Resource
Do you or a loved one want to add your own input events, or be sure to not conflict with BindAid? Then look no further!
BindAid does require that it be supported by modders. I cannot create a solution that would make this work for everything. As long as nothing too weird is being done, there should already be compatibility. But to take advantage of the new input handling method, support must be added.
The good news is, this can be done easily and in a way that doesn't make BindAid a hard requirement!
## Keyboard Compatibility
**DO:**
Feel free to make changes to Vanilla functions during `OnGameStart` or before, and be sure to forward your changes!
```lua
local _ItemBindingHandler_onKeyPressed = ItemBindingHandler.onKeyPressed
ItemBindingHandler.onKeyPressed = function(key)
_ItemBindingHandler_onKeyPressed(key)
end
```
Here, I have replaced the `ItemBindingHandler.onKeyPressed` with my own version of the function, and my change is now forwarded as the new `ItemBindingHandler.onKeyPressed` going forward.
**DON'T:** Make changes to Vanilla functions locally, and not forward your changes.
```lua
local _ItemBindingHandler_onKeyPressed = ItemBindingHandler.onKeyPressed
Events.OnKeyPressed.Remove(ItemBindingHandler.onKeyPressed)
local myNewCoolFunc = function(key)
_ItemBindingHandler_onKeyPressed(key)
end
Events.OnKeyPressed.Add(myNewCoolFunc)
```
This also prevents other functions from accessing any of your changes, as they will only get the vanilla version of the function and also would not be used at all. This is a sure-fire way to introduce incompatibilities!
The Key Event cache is built at the END of `OnGameStart`, so make any changes that you need before then!
That's basically it as far as compatibility: just make sure you forward your changes made to vanilla functions and be sure to make your changes when it makes sense to, and you're all set!
## Mod Load Order
There seems to be some confusion on this topic, likely due to how other games handle mods. But to be clear: Mod Load Order will never change the order that Lua files are loaded. If there is a code conflict, changing the Mod Load Order will not resolve this.
# How to use
Support for this mod can be added with just a few additional lines of code! First: get the module, if available. These will be referenced later.
- For Keyboard Input:
```lua
local keyman = (getActivatedMods():contains("BindAid") and require("KeybindManager"))
```
- For Mouse Input:
```lua
local mouse = (getActivatedMods():contains("BindAid") and require("Mouse"))
```
## Adding a New Key
You can add a new key to the game with the following function:
```lua
keyman.addKeybind("[SECTION]", "Key_Name", 0)
```
`[SECTION]` can be any of the available sections, or a new one! This will add the key to the appropriate section on the Keybind Options Tab.
At this point, even if you have already added the key another way, you would typically add a function to an Event to handle your key as well. Instead, we're going to add our key this way:
```lua
keyman.addKeyEvents("Key_Name", {
["OnKeyStartPressed"] = myDownFunction,
["OnKeyKeepPressed"] = myHoldFunction,
["OnKeyPressed"] = myUpFunction,
})
```
You **ONLY** need to add the events that you intend to support!
You can do this in all one go, too!
```lua
keyman.addKeybind("[SECTION]", "Key_Name", 0, {
["OnKeyStartPressed"] = myDownFunction,
["OnKeyKeepPressed"] = myHoldFunction,
["OnKeyPressed"] = myUpFunction,
})
```
If you are **ADDING** a new key bind, it MUST be done during or before `OnGameBoot` to ensure that the game receives this!
**NOTE:** If a user has the Keyboard Optimize disabled, then your keys will be added as normal events. You will need to plan for this in your function, by first checking the key pressed.
A full example:
```lua
-- Demonstrates adding Optional support for BindAid
local keyman = (getActivatedMods():contains("BindAid") and require("KeybindManager"))
local myKeyFunction = function(key)
if key == getCore():getKey("My_Key") then
getPlayer():Say("Test Pressed!")
elseif key == getCore():getKey("My_Key2") then
getPlayer():Say("Test2 Pressed!")
end
end
local onGameBoot = function()
-- This is just our Header for the keybinds
table.insert(keyBinding, { value = "[Testing]" })
if keyman then
-- Add our very own key, in full!
keyman.addKeybind("[Testing]", "My_Key", 82, {
["OnKeyPressed"] = myKeyFunction
})
-- Let's say we add our key another way...
table.insert(keyBinding, { value = "My_Key2", key = 83 })
-- Then we add our "Event" this way:
keyman.addKeyEvents("My_Key2", {
["OnKeyPressed"] = myKeyFunction
})
else
-- Add our keys and our events
table.insert(keyBinding, { value = "My_Key", key = 82 })
table.insert(keyBinding, { value = "My_Key2", key = 83 })
-- Add our Event
Events.OnKeyPressed.Add(myKeyFunction)
end
end
Events.OnGameBoot.Add(onGameBoot)
```
## Mouse Buttons
Mouse Button Events are still be developed, in that I am trying to make it easier to handle when a Mouse Button is not available.
For now, you have access to the following variable to tell you how many Mouse Buttons are enabled:
```lua
mouse.mouseButtons
```
Mouse Events work similarly to key bind events.
### Events
| Down | Hold | Release |
| ----------- | ----------- | ----------- |
| OnMouseMiddleDown | OnMouseMiddleHold | OnMouseMiddleUp |
| OnMouse4Down | OnMouse4Hold | OnMouse4Up |
| OnMouse5Down | OnMouse5Hold | OnMouse5Up |
| OnMouse6Down | OnMouse6Hold | OnMouse6Up |
| OnMouse7Down | OnMouse7Hold | OnMouse7Up |
| OnMouse8Down | OnMouse8Hold | OnMouse8Up |
| OnMouse9Down | OnMouse9Hold | OnMouse9Up |
| OnMouse10Down | OnMouse10Hold | OnMouse10Up |
| OnMouse11Down | OnMouse11Hold | OnMouse11Up |
| OnMouse12Down | OnMouse12Hold | OnMouse12Up |
The default Left and Right Click actions are defined in Java, and cannot be overwritten easily. It is beyond the scope of this mod to attempt this as well.
An example:
```lua
local mouse = (getActivatedMods():contains("BindAid") and require("Mouse"))
local myMouseButton = function(mouseX, mouseY)
getPlayer():Say("Middle Mouse!")
end
local onGameBoot = function()
if mouse then
mouse.Add({
["OnMouseMiddleUp"] = myMouseButton,
})
end
end
Events.OnGameBoot.Add(onGameBoot)
```
I also would like to add support for detecting when these Mouse Buttons are clicked on a UI, but it seems the base game only performs these checks on the main Mouse Buttons. So, stay tuned!

BIN
BindAid/preview.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

93
BindAid/workshop.txt Normal file
View File

@@ -0,0 +1,93 @@
version=1
id=2945066057
title=BindAid
description=[h1]Supports B41+. Works in Multiplayer[/h1]
description=[h3]A new save is not required[/h3]
description=
description=[img]https://i.imgur.com/p7Fv1Z6.gif[/img]
description=
description=Input Binding Manager for Project Zomboid which provides optimizations for keyboard input events and additional Mouse button support with optional keybaord emulation.
description=
description=[h2]Features[/h2]
description=[list]
description=[*] Keyboard Input Optimization
description=[*] Modkey Support
description=[*] Additional Mouse Button Support
description=[*] Mouse Button Keyboard Emulation
description=[*] Autohide the Mouse Cursor
description=[*] Easy API for Modders!
description=[/list]
description=
description=[h2]Mod Options[/h2]
description=
description=Mod Options is [b]REQUIRED[/b] to configure this mod; however, it is not a requirement for its operation. When your first install this mod, please take some time to set up its configuration based on your own needs using Mod Options. Further information on the available options is available below.
description=
description=When Mod Options is [b]NOT[/b] enabled for a save, your configuration will still be read and applied. [b]THIS MEANS[/b] Server Owners can safely include this mod without also requiring Mod Options, and user configuration will still be used.
description=
description=[h2]Keyboard Input Optimization[/h2]
description=
description=A full breakdown is available here: BindAid's Keyboard Input Optimization - What It Be, and How It Do
description=
description=In summary, there are 4 general events that are triggered for input handling, and each event will call multiple functions for each keypress.
description=[table]
description=[tr]
description=[th]Event[/th]
description=[th]Trigger[/th]
description=[th]# of Functions[/th]
description=[/tr]
description=[tr]
description=[td]OnKeyStartPressed[/td]
description=[td]When a key is pressed[/td]
description=[td]10[/td]
description=[/tr]
description=[tr]
description=[td]OnKeyKeepPressed[/td]
description=[td]When a key is held, every frame[/td]
description=[td]9[/td]
description=[/tr]
description=[tr]
description=[td]OnKeyPressed[/td]
description=[td]When a key is released[/td]
description=[td]26[/td]
description=[/tr]
description=[tr]
description=[td]OnCustomUIKey[/td]
description=[td]When a key is released[/td]
description=[td]1[/td]
description=[/tr]
description=[/table]
description=
description=This occurs for [b]ALL[/b] keypresses, whether they have actions bound or not. Multiple keypresses multiply the number of functions called per frame. This is not an issue for most people, but I have organized and optimized how this is handled so only 1 function is called during the event, which then calls the keypress if applicable.
description=
description=This feature can be disabled in the Mod Options configuration. If changed while in-game, a restart is required.
description=
description=[h3]Compatibility[/h3]
description=
description=I have created this in a way that should have maximum compatibility. If you have any issues with a vanilla key not being triggered, I would recommend disabling this optimization and letting me know. I cannot fix all mod compatibility, and some may require changes by the Modders. For Modders, please see: BindAid - Modder's Resource
description=
description=[list]
description=[*] Mods that add their own [b]NEW[/b] keys are not affected.
description=[*] Mods that modify vanilla functions for keypresses will be compatible as long as their changes are made no later than OnGameStart, and any modifications are forwarded.
description=[/list]
description=
description=[h2]Mouse Buttons[/h2]
description=
description=That's right, BindAid provides support for additional Mouse Buttons! Currently supports up to 10 additional Mouse Buttons, configurable in the Mod Options!
description=
description=You will need to select the number of Mouse Buttons that you would like to have support for in the Mod Options. A restart of the game is required after you have made this change.
description=
description=The Mouse Buttons have 2 different modes: Mouse Button Events (default) and Keyboard Emulation. An option is available to enable both methods to run as well.
description=
description=[h3]Mouse Button Events[/h3]
description=
description=When Mouse Button Events are in use, the additional Mouse Buttons are handled similarly to keybind events. Modders can hook into these events to allow additional actions to be done on a Mouse Button Press/Hold/Release. Please see: BindAid - Modder's Resource
description=
description=[b]THIS MEANS, BY DEFAULT, MOUSE BUTTONS PERFORM NO NEW ACTIONS! MODDERS MUST ADD SUPPORT FOR THIS![/b]
description=
description=[h3]Keyboard Emulation[/h3]
description=
description=New in the "Keybinds" Options Tab will be the Mouse Buttons that are supported under the "BindAid" section, each set to a keybind of "None". Setting a Key here on a Mouse Button will instead cause an emulated Keypress to be done instead of the Mouse Event! For example, you can bind "Reload" to the Middle Mouse Button.
description=
description=[b]NOTE!!![/b] The Emulated Keypress is ONLY done in Lua, so it will not trigger functions defined in Java. This means you cannot assign a key to "Move Forward" or something like that; it won't work. Actions such as Reloading, Shout/Emote, and many others are available, as well as new keybinds added by other mods (the main target).
tags=Build 41;Interface;Misc
visibility=private

BIN
like_fav.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 434 KiB