From b1393ee4a653c838a23f779b8cc4961e683b2b4a Mon Sep 17 00:00:00 2001
From: dg <dg@51575b47-30f0-44d4-a5cc-537603b46e54>
Date: Tue, 12 Jan 2010 14:18:04 +0000
Subject: [PATCH] KeyBind

git-svn-id: http://svn.net-core.org/repos/t-engine4@231 51575b47-30f0-44d4-a5cc-537603b46e54
---
 game/data/keybinds/actions.lua      |  54 +++++++
 game/data/keybinds/debug.lua        |   6 +
 game/data/keybinds/hotkeys.lua      | 221 ++++++++++++++++++++++++++
 game/data/keybinds/inventory.lua    |  45 ++++++
 game/data/keybinds/move.lua         | 106 +++++++++++++
 game/data/locales/number_hotkey.lua |  21 ---
 game/engine/KeyBind.lua             | 101 ++++++++++++
 game/engine/init.lua                |   8 +-
 game/modules/tome/class/Game.lua    | 234 ++++++++++------------------
 game/modules/tome/class/Player.lua  |  69 ++++++++
 game/modules/tome/load.lua          |   4 +
 11 files changed, 693 insertions(+), 176 deletions(-)
 create mode 100644 game/data/keybinds/actions.lua
 create mode 100644 game/data/keybinds/debug.lua
 create mode 100644 game/data/keybinds/hotkeys.lua
 create mode 100644 game/data/keybinds/inventory.lua
 create mode 100644 game/data/keybinds/move.lua
 delete mode 100644 game/data/locales/number_hotkey.lua
 create mode 100644 game/engine/KeyBind.lua

diff --git a/game/data/keybinds/actions.lua b/game/data/keybinds/actions.lua
new file mode 100644
index 0000000000..c41a6e9fdb
--- /dev/null
+++ b/game/data/keybinds/actions.lua
@@ -0,0 +1,54 @@
+defineAction{
+	default = { "uni:<", "uni:>" },
+	type = "CHANGE_LEVEL",
+	group = "actions",
+	name = "Go to next/previous level",
+}
+
+defineAction{
+	default = { "uni:G" },
+	type = "LEVELUP",
+	group = "actions",
+	name = "Levelup window",
+}
+defineAction{
+	default = { "uni:m" },
+	type = "USE_TALENTS",
+	group = "actions",
+	name = "Use talents",
+}
+
+defineAction{
+	default = { "uni:R" },
+	type = "REST",
+	group = "actions",
+	name = "Rest for a while",
+}
+
+defineAction{
+	default = { "sym:115:true:false:false:false" },
+	type = "SAVE_GAME",
+	group = "actions",
+	name = "Save game",
+}
+
+defineAction{
+	default = { "sym:120:true:false:false:false" },
+	type = "QUIT_GAME",
+	group = "actions",
+	name = "Quit game",
+}
+
+defineAction{
+	default = { "sym:116:true:false:false:false" },
+	type = "SHOW_TIME",
+	group = "actions",
+	name = "Show game calendar",
+}
+
+defineAction{
+	default = { "sym:115:false:false:true:false" },
+	type = "SWITCH_GFX",
+	group = "actions",
+	name = "Switch graphical modes",
+}
diff --git a/game/data/keybinds/debug.lua b/game/data/keybinds/debug.lua
new file mode 100644
index 0000000000..c4c69e2d03
--- /dev/null
+++ b/game/data/keybinds/debug.lua
@@ -0,0 +1,6 @@
+defineAction{
+	default = { "sym:108:true:false:false:false" },
+	type = "LUA_CONSOLE",
+	group = "debug",
+	name = "Show Lua console",
+}
diff --git a/game/data/keybinds/hotkeys.lua b/game/data/keybinds/hotkeys.lua
new file mode 100644
index 0000000000..501be6b73c
--- /dev/null
+++ b/game/data/keybinds/hotkeys.lua
@@ -0,0 +1,221 @@
+-- Hotkeys
+defineAction{
+	default = { "sym:49:false:false:false:false" },
+	type = "HOTKEY_1",
+	group = "hotkeys",
+	name = "Hotkey 1",
+}
+defineAction{
+	default = { "sym:50:false:false:false:false" },
+	type = "HOTKEY_2",
+	group = "hotkeys",
+	name = "Hotkey 2",
+}
+defineAction{
+	default = { "sym:51:false:false:false:false" },
+	type = "HOTKEY_3",
+	group = "hotkeys",
+	name = "Hotkey 3",
+}
+defineAction{
+	default = { "sym:52:false:false:false:false" },
+	type = "HOTKEY_4",
+	group = "hotkeys",
+	name = "Hotkey 4",
+}
+defineAction{
+	default = { "sym:53:false:false:false:false" },
+	type = "HOTKEY_5",
+	group = "hotkeys",
+	name = "Hotkey 5",
+}
+defineAction{
+	default = { "sym:54:false:false:false:false" },
+	type = "HOTKEY_6",
+	group = "hotkeys",
+	name = "Hotkey 6",
+}
+defineAction{
+	default = { "sym:55:false:false:false:false" },
+	type = "HOTKEY_7",
+	group = "hotkeys",
+	name = "Hotkey 7",
+}
+defineAction{
+	default = { "sym:56:false:false:false:false" },
+	type = "HOTKEY_8",
+	group = "hotkeys",
+	name = "Hotkey 8",
+}
+defineAction{
+	default = { "sym:57:false:false:false:false" },
+	type = "HOTKEY_9",
+	group = "hotkeys",
+	name = "Hotkey 9",
+}
+defineAction{
+	default = { "sym:48:false:false:false:false" },
+	type = "HOTKEY_10",
+	group = "hotkeys",
+	name = "Hotkey 10",
+}
+defineAction{
+	default = { "sym:41:false:false:false:false" },
+	type = "HOTKEY_11",
+	group = "hotkeys",
+	name = "Hotkey 11",
+}
+defineAction{
+	default = { "sym:61:false:false:false:false" },
+	type = "HOTKEY_12",
+	group = "hotkeys",
+	name = "Hotkey 12",
+}
+
+-- Ctrl + Hotkeys
+defineAction{
+	default = { "sym:49:true:false:false:false" },
+	type = "HOTKEY_SECOND_1",
+	group = "hotkeys",
+	name = "Secondary Hotkey 1",
+}
+defineAction{
+	default = { "sym:50:true:false:false:false" },
+	type = "HOTKEY_SECOND_2",
+	group = "hotkeys",
+	name = "Secondary Hotkey 2",
+}
+defineAction{
+	default = { "sym:51:true:false:false:false" },
+	type = "HOTKEY_SECOND_3",
+	group = "hotkeys",
+	name = "Secondary Hotkey 3",
+}
+defineAction{
+	default = { "sym:52:true:false:false:false" },
+	type = "HOTKEY_SECOND_4",
+	group = "hotkeys",
+	name = "Secondary Hotkey 4",
+}
+defineAction{
+	default = { "sym:53:true:false:false:false" },
+	type = "HOTKEY_SECOND_5",
+	group = "hotkeys",
+	name = "Secondary Hotkey 5",
+}
+defineAction{
+	default = { "sym:54:true:false:false:false" },
+	type = "HOTKEY_SECOND_6",
+	group = "hotkeys",
+	name = "Secondary Hotkey 6",
+}
+defineAction{
+	default = { "sym:55:true:false:false:false" },
+	type = "HOTKEY_SECOND_7",
+	group = "hotkeys",
+	name = "Secondary Hotkey 7",
+}
+defineAction{
+	default = { "sym:56:true:false:false:false" },
+	type = "HOTKEY_SECOND_8",
+	group = "hotkeys",
+	name = "Secondary Hotkey 8",
+}
+defineAction{
+	default = { "sym:57:true:false:false:false" },
+	type = "HOTKEY_SECOND_9",
+	group = "hotkeys",
+	name = "Secondary Hotkey 9",
+}
+defineAction{
+	default = { "sym:48:true:false:false:false" },
+	type = "HOTKEY_SECOND_10",
+	group = "hotkeys",
+	name = "Secondary Hotkey 10",
+}
+defineAction{
+	default = { "sym:41:true:false:false:false" },
+	type = "HOTKEY_SECOND_11",
+	group = "hotkeys",
+	name = "Secondary Hotkey 11",
+}
+defineAction{
+	default = { "sym:61:true:false:false:false" },
+	type = "HOTKEY_SECOND_12",
+	group = "hotkeys",
+	name = "Secondary Hotkey 12",
+}
+
+-- Shift + Hotkeys
+defineAction{
+	default = { "sym:49:false:true:false:false" },
+	type = "HOTKEY_THIRD_1",
+	group = "hotkeys",
+	name = "Third Hotkey 1",
+}
+defineAction{
+	default = { "sym:50:false:true:false:false" },
+	type = "HOTKEY_THIRD_2",
+	group = "hotkeys",
+	name = "Third Hotkey 2",
+}
+defineAction{
+	default = { "sym:51:false:true:false:false" },
+	type = "HOTKEY_THIRD_3",
+	group = "hotkeys",
+	name = "Third Hotkey 3",
+}
+defineAction{
+	default = { "sym:52:false:true:false:false" },
+	type = "HOTKEY_THIRD_4",
+	group = "hotkeys",
+	name = "Third Hotkey 4",
+}
+defineAction{
+	default = { "sym:53:false:true:false:false" },
+	type = "HOTKEY_THIRD_5",
+	group = "hotkeys",
+	name = "Third Hotkey 5",
+}
+defineAction{
+	default = { "sym:54:false:true:false:false" },
+	type = "HOTKEY_THIRD_6",
+	group = "hotkeys",
+	name = "Third Hotkey 6",
+}
+defineAction{
+	default = { "sym:55:false:true:false:false" },
+	type = "HOTKEY_THIRD_7",
+	group = "hotkeys",
+	name = "Third Hotkey 7",
+}
+defineAction{
+	default = { "sym:56:false:true:false:false" },
+	type = "HOTKEY_THIRD_8",
+	group = "hotkeys",
+	name = "Third Hotkey 8",
+}
+defineAction{
+	default = { "sym:57:false:true:false:false" },
+	type = "HOTKEY_THIRD_9",
+	group = "hotkeys",
+	name = "Third Hotkey 9",
+}
+defineAction{
+	default = { "sym:48:false:true:false:false" },
+	type = "HOTKEY_THIRD_10",
+	group = "hotkeys",
+	name = "Third Hotkey 10",
+}
+defineAction{
+	default = { "sym:41:false:true:false:false" },
+	type = "HOTKEY_THIRD_11",
+	group = "hotkeys",
+	name = "Third Hotkey 11",
+}
+defineAction{
+	default = { "sym:61:false:true:false:false" },
+	type = "HOTKEY_THIRD_12",
+	group = "hotkeys",
+	name = "Third Hotkey 12",
+}
diff --git a/game/data/keybinds/inventory.lua b/game/data/keybinds/inventory.lua
new file mode 100644
index 0000000000..0d366c2274
--- /dev/null
+++ b/game/data/keybinds/inventory.lua
@@ -0,0 +1,45 @@
+defineAction{
+	default = { "uni:i", },
+	type = "SHOW_INVENTORY",
+	group = "inventory",
+	name = "shows inventory",
+}
+defineAction{
+	default = { "uni:e", },
+	type = "SHOW_EQUIPMENT",
+	group = "inventory",
+	name = "shows equipment",
+}
+
+defineAction{
+	default = { "uni:g" },
+	type = "PICKUP_FLOOR",
+	group = "actions",
+	name = "Pickup items",
+}
+defineAction{
+	default = { "uni:d" },
+	type = "DROP_FLOOR",
+	group = "actions",
+	name = "Drop items",
+}
+
+defineAction{
+	default = { "uni:w", },
+	type = "WEAR_ITEM",
+	group = "inventory",
+	name = "wield/wear items",
+}
+defineAction{
+	default = { "uni:t", },
+	type = "TAKEOFF_ITEM",
+	group = "inventory",
+	name = "takeoff items",
+}
+
+defineAction{
+	default = { "uni:u", },
+	type = "USE_ITEM",
+	group = "inventory",
+	name = "use items",
+}
diff --git a/game/data/keybinds/move.lua b/game/data/keybinds/move.lua
new file mode 100644
index 0000000000..0b54626bb5
--- /dev/null
+++ b/game/data/keybinds/move.lua
@@ -0,0 +1,106 @@
+-- Character movements
+defineAction{
+	default = { "sym:276:false:false:false:false", "sym:260:false:false:false:false" },
+	type = "MOVE_LEFT",
+	group = "movement",
+	name = "Move left",
+}
+defineAction{
+	default = { "sym:275:false:false:false:false", "sym:262:false:false:false:false" },
+	type = "MOVE_RIGHT",
+	group = "movement",
+	name = "Move right",
+}
+defineAction{
+	default = { "sym:273:false:false:false:false", "sym:264:false:false:false:false" },
+	type = "MOVE_UP",
+	group = "movement",
+	name = "Move up",
+}
+defineAction{
+	default = { "sym:274:false:false:false:false", "sym:258:false:false:false:false" },
+	type = "MOVE_DOWN",
+	group = "movement",
+	name = "Move down",
+}
+defineAction{
+	default = { "sym:263:false:false:false:false" },
+	type = "MOVE_LEFT_UP",
+	group = "movement",
+	name = "Move diagonaly left and up",
+}
+defineAction{
+	default = { "sym:265:false:false:false:false" },
+	type = "MOVE_RIGHT_UP",
+	group = "movement",
+	name = "Move diagonaly right and up",
+}
+defineAction{
+	default = { "sym:257:false:false:false:false" },
+	type = "MOVE_LEFT_DOWN",
+	group = "movement",
+	name = "Move diagonaly left and down",
+}
+defineAction{
+	default = { "sym:259:false:false:false:false" },
+	type = "MOVE_RIGHT_DOWN",
+	group = "movement",
+	name = "Move diagonaly right and down",
+}
+
+defineAction{
+	default = { "sym:261:false:false:false:false" },
+	type = "MOVE_STAY",
+	group = "movement",
+	name = "Stay for a turn",
+}
+
+-- Running
+defineAction{
+	default = { "sym:276:false:true:false:false", "sym:260:false:true:false:false" },
+	type = "RUN_LEFT",
+	group = "movement",
+	name = "Run left",
+}
+defineAction{
+	default = { "sym:275:false:true:false:false", "sym:262:false:true:false:false" },
+	type = "RUN_RIGHT",
+	group = "movement",
+	name = "Run right",
+}
+defineAction{
+	default = { "sym:273:false:true:false:false", "sym:264:false:true:false:false" },
+	type = "RUN_UP",
+	group = "movement",
+	name = "Run up",
+}
+defineAction{
+	default = { "sym:274:false:true:false:false", "sym:258:false:true:false:false" },
+	type = "RUN_DOWN",
+	group = "movement",
+	name = "Run down",
+}
+defineAction{
+	default = { "sym:263:false:true:false:false" },
+	type = "RUN_LEFT_UP",
+	group = "movement",
+	name = "Run diagonaly left and up",
+}
+defineAction{
+	default = { "sym:265:false:true:false:false" },
+	type = "RUN_RIGHT_UP",
+	group = "movement",
+	name = "Run diagonaly right and up",
+}
+defineAction{
+	default = { "sym:257:false:true:false:false" },
+	type = "RUN_LEFT_DOWN",
+	group = "movement",
+	name = "Run diagonaly left and down",
+}
+defineAction{
+	default = { "sym:259:false:true:false:false" },
+	type = "RUN_RIGHT_DOWN",
+	group = "movement",
+	name = "Run diagonaly right and down",
+}
diff --git a/game/data/locales/number_hotkey.lua b/game/data/locales/number_hotkey.lua
deleted file mode 100644
index 34cadcafaf..0000000000
--- a/game/data/locales/number_hotkey.lua
+++ /dev/null
@@ -1,21 +0,0 @@
--- Convert unicode keys to other unicode keys depending on locale
--- This allows to support the number bar "hotkeys" without too much trouble
-if locale == "fr_FR" then
-	return {
-		[_AMPERSAND] =	_1,
-		[_WORLD_73] =	_2,
-		[_QUOTEDBL] =	_3,
-		[_QUOTE] =	_4,
-		[_LEFTPAREN] =	_5,
-		[_MINUS] =	_6,
-		[_WORLD_72] =	_7,
-		[_UNDERSCORE] =	_8,
-		[_WORLD_71] =	_9,
-		[_WORLD_64] =	_0,
---		[_RIGHTPAREN] =	_),
---		[_EQUALS] =	_=,
-	}
-else
-	-- Default to no convertion
-	return {}
-end
diff --git a/game/engine/KeyBind.lua b/game/engine/KeyBind.lua
new file mode 100644
index 0000000000..2e192d0c38
--- /dev/null
+++ b/game/engine/KeyBind.lua
@@ -0,0 +1,101 @@
+require "config"
+require "engine.class"
+require "engine.KeyCommand"
+
+--- Handles key binds to "virtual" actions
+module(..., package.seeall, class.inherit(engine.KeyCommand))
+
+_M.binds_def = {}
+_M.binds_remap = {}
+_M.binds_loaded = {}
+
+function _M:defineAction(t)
+	assert(t.default, "no keybind default")
+	assert(t.name, "no keybind name")
+	t.desc = t.desc or t.name
+
+	_M.binds_def[t.type] = t
+end
+
+--- Loads a list of keybind definitions
+-- Keybind definitions are in /data/keybinds/. Modules can define new ones.
+-- @param a string representing the keybind, separated by commas. I.e: "move,hotkeys,actions,inventory"
+function _M:load(str)
+	local defs = str:split(",")
+	for i, def in ipairs(defs) do
+		if not _M.binds_loaded[def] then
+			local f, err = loadfile("/data/keybinds/"..def..".lua")
+			if not f and err then error(err) end
+			setfenv(f, setmetatable({
+				defineAction = function(t) self:defineAction(t) end
+			}, {__index=_G}))
+			f()
+
+			print("[KEYBINDER] Loaded keybinds: "..def)
+			_M.binds_loaded[def] = true
+		end
+	end
+end
+
+--- Loads a keybinds remap
+function _M:loadRemap(file)
+	local f, err = loadfile(file)
+	if not f and err then error(err) end
+	local d = {}
+	setfenv(f, d)
+	f()
+
+	for virtual, keys in pairs(d) do
+		print("Remapping", virtual, keys)
+		_M.binds_remap[virtual] = keys
+	end
+end
+
+function _M:init()
+	engine.KeyCommand.init(self)
+	self.virtuals = {}
+	self.binds = {}
+
+	-- Bind defaults
+	for type, t in pairs(_M.binds_def) do
+		for i, ks in ipairs(_M.binds_remap[type] or t.default) do
+			self.binds[ks] = type
+		end
+	end
+end
+
+function _M:makeKeyString(sym, ctrl, shift, alt, meta, unicode)
+	return ("sym:%s:%s:%s:%s:%s"):format(tostring(sym), tostring(ctrl), tostring(shift), tostring(alt), tostring(meta)), unicode and "uni:"..unicode
+end
+
+function _M:receiveKey(sym, ctrl, shift, alt, meta, unicode)
+	local ks, us = self:makeKeyString(sym, ctrl, shift, alt, meta, unicode)
+	print("[BIND]", sym, ctrl, shift, alt, meta, unicode and string.byte(unicode), " :=: ", ks, us, " ?=? ", self.binds[ks], us and self.binds[us])
+	if self.binds[ks] and self.virtuals[self.binds[ks]] then
+		self.virtuals[self.binds[ks]](sym, ctrl, shift, alt, meta, unicode)
+		return
+	elseif us and self.binds[us] and self.virtuals[self.binds[us]] then
+		self.virtuals[self.binds[us]](sym, ctrl, shift, alt, meta, unicode)
+		return
+	end
+
+	engine.KeyCommand.receiveKey(self, sym, ctrl, shift, alt, meta, unicode)
+end
+
+--- Adds a key/command combinaison
+-- @param sym the key to handle
+-- @param mods a table with the mod keys needed, i.e: {"ctrl", "alt"}
+-- @param fct the function to call when the key is pressed
+function _M:addBind(virtual, fct)
+	self.virtuals[virtual] = fct
+end
+
+--- Adds a key/command combinaison
+-- @param sym the key to handle
+-- @param mods a table with the mod keys needed, i.e: {"ctrl", "alt"}
+-- @param fct the function to call when the key is pressed
+function _M:addBinds(t)
+	for virtual, fct in pairs(t) do
+		self:addBind(virtual, fct)
+	end
+end
diff --git a/game/engine/init.lua b/game/engine/init.lua
index 60d70ec012..7f9669321b 100644
--- a/game/engine/init.lua
+++ b/game/engine/init.lua
@@ -7,7 +7,7 @@ dofile("/engine/resolvers.lua")
 
 require "config"
 require "engine.Game"
-require "engine.KeyCommand"
+require "engine.KeyBind"
 require "engine.Savefile"
 require "engine.Tiles"
 engine.Tiles.prefix = "/data/gfx/"
@@ -26,10 +26,14 @@ keyboard.locale = "en_US"
 window.size = "1024x768"
 ]]
 config.load("/settings.cfg")
+
+-- Load remaps
+engine.KeyBind:loadRemap("/keybinds.cfg")
+
 fs.umount(engine.homepath)
 
 -- Setup a default key handler
-local key = engine.KeyCommand.new()
+local key = engine.KeyBind.new()
 key:setCurrent()
 
 -- Load the game module
diff --git a/game/modules/tome/class/Game.lua b/game/modules/tome/class/Game.lua
index 9a37e31dc9..cbb757e44a 100644
--- a/game/modules/tome/class/Game.lua
+++ b/game/modules/tome/class/Game.lua
@@ -1,6 +1,6 @@
 require "engine.class"
 require "engine.GameTurnBased"
-require "engine.KeyCommand"
+require "engine.KeyBind"
 local Savefile = require "engine.Savefile"
 local DamageType = require "engine.DamageType"
 local Zone = require "engine.Zone"
@@ -37,7 +37,7 @@ module(..., package.seeall, class.inherit(engine.GameTurnBased))
 collectgarbage("stop")
 
 function _M:init()
-	engine.GameTurnBased.init(self, engine.KeyCommand.new(), 1000, 100)
+	engine.GameTurnBased.init(self, engine.KeyBind.new(), 1000, 100)
 
 	-- Same init as when loaded from a savefile
 	self:loaded()
@@ -99,7 +99,7 @@ function _M:loaded()
 	Zone:setup{npc_class="mod.class.NPC", grid_class="mod.class.Grid", object_class="mod.class.Object"}
 	Map:setViewerActor(self.player)
 	Map:setViewPort(200, 20, self.w - 200, math.floor(self.h * 0.80) - 20, 32, 32, nil, 20, true)
-	self.key = engine.KeyCommand.new()
+	self.key = engine.KeyBind.new()
 end
 
 function _M:onResolutionChange()
@@ -275,7 +275,7 @@ function _M:targetMode(v, msg, co, typ)
 end
 
 function _M:setupCommands()
-	self.targetmode_key = engine.KeyCommand.new()
+	self.targetmode_key = engine.KeyBind.new()
 	self.targetmode_key:addCommands
 	{
 		_t = function()
@@ -319,131 +319,87 @@ function _M:setupCommands()
 	}
 
 	self.normal_key = self.key
-	-- Load the locales
-	self.key:loadLocaleConvertion("/data/locales/number_hotkey.lua")
 	-- Activate profiler keybinds
 	self.key:setupProfiler()
-	self.key:addCommands
+
+	self.key:addBinds
 	{
-		_1 = function() self.player:activateHotkey(1) end,
-		_2 = function() self.player:activateHotkey(2) end,
-		_3 = function() self.player:activateHotkey(3) end,
-		_4 = function() self.player:activateHotkey(4) end,
-		_5 = function() self.player:activateHotkey(5) end,
-		_6 = function() self.player:activateHotkey(6) end,
-		_7 = function() self.player:activateHotkey(7) end,
-		_8 = function() self.player:activateHotkey(8) end,
-		_9 = function() self.player:activateHotkey(9) end,
-		_0 = function() self.player:activateHotkey(10) end,
-		_RIGHTPAREN = function() self.player:activateHotkey(11) end,
-		_EQUALS = function() self.player:activateHotkey(12) end,
-
-		-- running
-		[{"_LEFT","shift"}] = function() self.player:runInit(4) end,
-		[{"_RIGHT","shift"}] = function() self.player:runInit(6) end,
-		[{"_UP","shift"}] = function() self.player:runInit(8) end,
-		[{"_DOWN","shift"}] = function() self.player:runInit(2) end,
-		[{"_KP4","shift"}] = function() self.player:runInit(4) end,
-		[{"_KP6","shift"}] = function() self.player:runInit(6) end,
-		[{"_KP8","shift"}] = function() self.player:runInit(8) end,
-		[{"_KP2","shift"}] = function() self.player:runInit(2) end,
-		[{"_KP1","shift"}] = function() self.player:runInit(1) end,
-		[{"_KP3","shift"}] = function() self.player:runInit(3) end,
-		[{"_KP7","shift"}] = function() self.player:runInit(7) end,
-		[{"_KP9","shift"}] = function() self.player:runInit(9) end,
-
-		-- resting
-		[{"_r","shift"}] = function()
-			self.player:restInit()
+		-- Movements
+		MOVE_LEFT = function() self.player:moveDir(4) end,
+		MOVE_RIGHT = function() self.player:moveDir(6) end,
+		MOVE_UP = function() self.player:moveDir(8) end,
+		MOVE_DOWN = function() self.player:moveDir(2) end,
+		MOVE_LEFT_UP = function() self.player:moveDir(7) end,
+		MOVE_LEFT_DOWN = function() self.player:moveDir(1) end,
+		MOVE_RIGHT_UP = function() self.player:moveDir(9) end,
+		MOVE_RIGHT_DOWN = function() self.player:moveDir(3) end,
+		MOVE_STAY = function() self.player:useEnergy() end,
+
+		RUN_LEFT = function() self.player:runInit(4) end,
+		RUN_RIGHT = function() self.player:runInit(6) end,
+		RUN_UP = function() self.player:runInit(8) end,
+		RUN_DOWN = function() self.player:runInit(2) end,
+		RUN_LEFT_UP = function() self.player:runInit(7) end,
+		RUN_LEFT_DOWN = function() self.player:runInit(1) end,
+		RUN_RIGHT_UP = function() self.player:runInit(9) end,
+		RUN_RIGHT_DOWN = function() self.player:runInit(3) end,
+
+		-- Hotkeys
+		HOTKEY_1 = function() self.player:activateHotkey(1) end,
+		HOTKEY_2 = function() self.player:activateHotkey(2) end,
+		HOTKEY_3 = function() self.player:activateHotkey(3) end,
+		HOTKEY_4 = function() self.player:activateHotkey(4) end,
+		HOTKEY_5 = function() self.player:activateHotkey(5) end,
+		HOTKEY_6 = function() self.player:activateHotkey(6) end,
+		HOTKEY_7 = function() self.player:activateHotkey(7) end,
+		HOTKEY_8 = function() self.player:activateHotkey(8) end,
+		HOTKEY_9 = function() self.player:activateHotkey(9) end,
+		HOTKEY_10 = function() self.player:activateHotkey(10) end,
+		HOTKEY_11 = function() self.player:activateHotkey(11) end,
+		HOTKEY_12 = function() self.player:activateHotkey(12) end,
+
+		-- Actions
+		CHANGE_LEVEL = function()
+			local e = self.level.map(self.player.x, self.player.y, Map.TERRAIN)
+			if self.player:enoughEnergy() and e.change_level then
+				-- Do not unpause, the player is allowed first move on next level
+				self:changeLevel(e.change_zone and e.change_level or self.level.level + e.change_level, e.change_zone)
+			else
+				self.log("There is no way out of this level here.")
+			end
 		end,
 
-		-- talent use
-		_m = function()
-			self.player:useTalents()
+		REST = function()
+			self.player:restInit()
 		end,
 
-		-- Pickup object
-		_g = function()
-			-- If 2 or more objects, display a pickup dialog, otehrwise just picks up
-			if self.level.map:getObject(self.player.x, self.player.y, 2) then
-				self.player:showPickupFloor(nil, nil, function(o, item)
-					self.player:pickupFloor(item, true)
-					self.player:sortInven()
-					self.player:useEnergy()
-				end)
-			else
-				self.player:pickupFloor(1, true)
-				self.player:sortInven()
-			end
+		PICKUP_FLOOR = function()
+			self.player:playerPickup()
 		end,
-
-		-- Show inventory
-		_i = function()
+		DROP_FLOOR = function()
+			self.player:playerDrop()
+		end,
+		SHOW_INVENTORY = function()
 			self.player:showInventory(nil, self.player:getInven(self.player.INVEN_INVEN), nil, function() end)
 		end,
-		-- Show equipment
-		_e = function()
+		SHOW_EQUIPMENT = function()
 			self.player:showEquipment(nil, nil, function() end)
 		end,
-		-- Drop item
-		_d = function()
-			local inven = self.player:getInven(self.player.INVEN_INVEN)
-			self.player:showInventory("Drop object", inven, nil, function(o, item)
-				self.player:dropFloor(inven, item)
-				self.player:sortInven()
-				self.player:useEnergy()
-			end)
+		WEAR_ITEM = function()
+			self.player:playerWear()
 		end,
-		-- Wear item
-		_w = function()
-			local inven = self.player:getInven(self.player.INVEN_INVEN)
-			self.player:showInventory("Wield/wear object", inven, function(o)
-				return o:wornInven() and true or false
-			end, function(o, item)
-				local ro = self.player:wearObject(o, true, true)
-				if ro then
-					if type(ro) == "table" then self.player:addObject(self.player.INVEN_INVEN, ro) end
-					self.player:removeObject(self.player.INVEN_INVEN, item)
-				end
-				self.player:sortInven()
-				self.player:useEnergy()
-			end)
+		TAKEOFF_ITEM = function()
+			self.player:playerTakeoff()
 		end,
-		-- Takeoff item
-		_t = function()
-			self.player:showEquipment("Take off object", nil, function(o, inven, item)
-				if self.player:takeoffObject(inven, item) then
-					self.player:addObject(self.player.INVEN_INVEN, o)
-				end
-				self.player:sortInven()
-				self.player:useEnergy()
-			end)
+		USE_ITEM = function()
+			self.player:playerUseItem()
 		end,
 
-		-- Use item
-		_u = function()
-			self.player:showInventory(nil, self.player:getInven(self.player.INVEN_INVEN),
-				function(o)
-					return o:canUseObject()
-				end,
-				function(o, item)
-					local ret = o:use(self.player)
-					if ret and ret == "destroy" then
-						if o.multicharge and o.multicharge > 1 then
-							o.multicharge = o.multicharge - 1
-						else
-							self.player:removeObject(self.player:getInven(self.player.INVEN_INVEN), item)
-							self.log("You have no more "..o:getName())
-							self.player:sortInven()
-						end
-						self.player:useEnergy()
-					end
-				end
-			)
+		USE_TALENTS = function()
+			self.player:useTalents()
 		end,
 
-
-		[{"_g","shift"}] = function()
+		LEVELUP = function()
 			local none = true
 			if self.player.unused_stats > 0 then
 				local ds = LevelupStatsDialog.new(self.player)
@@ -459,22 +415,15 @@ function _M:setupCommands()
 			end
 		end,
 
-		_LEFT  = function() self.player:moveDir(4) end,
-		_RIGHT = function() self.player:moveDir(6) end,
-		_UP    = function() self.player:moveDir(8) end,
-		_DOWN  = function() self.player:moveDir(2) end,
-		_KP1   = function() self.player:moveDir(1) end,
-		_KP2   = function() self.player:moveDir(2) end,
-		_KP3   = function() self.player:moveDir(3) end,
-		_KP4   = function() self.player:moveDir(4) end,
-		_KP5   = function() self.player:moveDir(5) end,
-		_KP6   = function() self.player:moveDir(6) end,
-		_KP7   = function() self.player:moveDir(7) end,
-		_KP8   = function() self.player:moveDir(8) end,
-		_KP9   = function() self.player:moveDir(9) end,
+		SAVE_GAME = function()
+			local save = Savefile.new(self.save_name)
+			save:saveGame(self)
+			save:close()
+			self.log("Saved game.")
+		end,
 
 		-- Toggle tactical displau
-		[{"_t","shift"}] = function()
+		SHOW_TIME = function()
 			if Map.view_faction then
 				self:targetMode(false, true)
 				self.always_target = nil
@@ -486,25 +435,28 @@ function _M:setupCommands()
 			end
 		end,
 		-- Show time
-		[{"_t","ctrl"}] = function()
+		SHOW_TIME = function()
 			self.log(self.calendar:getTimeDate(self.turn))
 		end,
 		-- Exit the game
-		[{"_x","ctrl"}] = function()
+		QUIT_GAME = function()
 			self:onQuit()
 		end,
 		-- Lua console
-		[{"_l","ctrl"}] = function()
+		LUA_CONSOLE = function()
 			self:registerDialog(DebugConsole.new())
 		end,
 
 		-- Switch gfx modes
-		[{"_s","alt"}] = function()
+		SWITCH_GFX = function()
 			self.gfxmode = self.gfxmode or 1
 			self.gfxmode = util.boundWrap(self.gfxmode + 1, 1, 3)
 			self:setupDisplayMode()
 		end,
+	}
 
+	self.key:addCommands
+	{
 		-- Targeting movement
 		[{"_LEFT","ctrl","shift"}] = function() self.target.target.entity=nil self.target.target.x = self.target.target.x - 1 end,
 		[{"_RIGHT","ctrl","shift"}] = function() self.target.target.entity=nil self.target.target.x = self.target.target.x + 1 end,
@@ -530,30 +482,6 @@ function _M:setupCommands()
 		[{"_KP3","ctrl"}] = function() self.target:scan(3) end,
 		[{"_KP7","ctrl"}] = function() self.target:scan(7) end,
 		[{"_KP9","ctrl"}] = function() self.target:scan(9) end,
-
-		-- Save the game
-		[{"_s","ctrl"}] = function()
-			local save = Savefile.new(self.save_name)
-			save:saveGame(self)
-			save:close()
-			self.log("Saved game.")
-		end,
-
-		-- Default handler, we treat here some "weird" keys that do not seem too havdle well on different keyboard layouts
-		__DEFAULT = function(sym, ctrl, shift, alt, meta, unicode)
-			if unicode == "<" or unicode == ">" then
-				local e = self.level.map(self.player.x, self.player.y, Map.TERRAIN)
-				if self.player:enoughEnergy() and e.change_level then
-					-- Do not unpause, the player is allowed first move on next level
-					self:changeLevel(e.change_zone and e.change_level or self.level.level + e.change_level, e.change_zone)
-				else
-					self.log("There is no way out of this level here.")
-				end
-			end
-			if unicode == "r" then
-				self:setResolution("800x600")
-			end
-		end,
 	}
 
 	self.key:setCurrent()
diff --git a/game/modules/tome/class/Player.lua b/game/modules/tome/class/Player.lua
index 4ad776ede7..2a491ddd61 100644
--- a/game/modules/tome/class/Player.lua
+++ b/game/modules/tome/class/Player.lua
@@ -199,3 +199,72 @@ function _M:runCheck()
 
 	return engine.interface.PlayerRun.runCheck(self)
 end
+
+function _M:playerPickup()
+	-- If 2 or more objects, display a pickup dialog, otehrwise just picks up
+	if game.level.map:getObject(self.x, self.y, 2) then
+		self:showPickupFloor(nil, nil, function(o, item)
+			self:pickupFloor(item, true)
+			self:sortInven()
+		end)
+	else
+		self:pickupFloor(1, true)
+		self:sortInven()
+		self:useEnergy()
+	end
+end
+
+function _M:playerDrop()
+	local inven = self:getInven(self.INVEN_INVEN)
+	self:showInventory("Drop object", inven, nil, function(o, item)
+		self:dropFloor(inven, item)
+		self:sortInven()
+		self:useEnergy()
+	end)
+end
+
+function _M:playerWear()
+	local inven = self:getInven(self.INVEN_INVEN)
+	self:showInventory("Wield/wear object", inven, function(o)
+		return o:wornInven() and true or false
+	end, function(o, item)
+		local ro = self:wearObject(o, true, true)
+		if ro then
+			if type(ro) == "table" then self:addObject(self.INVEN_INVEN, ro) end
+			self:removeObject(self.INVEN_INVEN, item)
+		end
+		self:sortInven()
+		self:useEnergy()
+	end)
+end
+
+function _M:playerTakeoff()
+	self:showEquipment("Take off object", nil, function(o, inven, item)
+		if self:takeoffObject(inven, item) then
+			self:addObject(self.INVEN_INVEN, o)
+		end
+		self:sortInven()
+		self:useEnergy()
+	end)
+end
+
+function _M:playerUseItem()
+	self:showInventory(nil, self:getInven(self.INVEN_INVEN),
+		function(o)
+			return o:canUseObject()
+		end,
+		function(o, item)
+			local ret = o:use(self)
+			if ret and ret == "destroy" then
+				if o.multicharge and o.multicharge > 1 then
+					o.multicharge = o.multicharge - 1
+				else
+					self:removeObject(self:getInven(self.INVEN_INVEN), item)
+					game.log("You have no more "..o:getName())
+					self:sortInven()
+					self:useEnergy()
+				end
+			end
+		end
+	)
+end
diff --git a/game/modules/tome/load.lua b/game/modules/tome/load.lua
index 8f41709bbd..aba5ed693d 100644
--- a/game/modules/tome/load.lua
+++ b/game/modules/tome/load.lua
@@ -1,4 +1,5 @@
 -- This file loads the game module, and loads data
+local KeyBind = require "engine.KeyBind"
 local DamageType = require "engine.DamageType"
 local ActorStats = require "engine.interface.ActorStats"
 local ActorResource = require "engine.interface.ActorResource"
@@ -8,6 +9,9 @@ local ActorAI = require "engine.interface.ActorAI"
 local ActorInventory = require "engine.interface.ActorInventory"
 local Birther = require "engine.Birther"
 
+-- Usefull keybinds
+KeyBind:load("move,hotkeys,inventory,actions,debug")
+
 -- Some default colors
 dofile("/mod/colors.lua")
 
-- 
GitLab