1/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
2/* If you are missing that file, acquire a complete release at teeworlds.com. */
3#include "binds.h"
4#include <base/system.h>
5#include <engine/config.h>
6#include <engine/shared/config.h>
7
8#include <game/client/gameclient.h>
9
10static const ColorRGBA gs_BindPrintColor{1.0f, 1.0f, 0.8f, 1.0f};
11
12bool CBinds::CBindsSpecial::OnInput(const IInput::CEvent &Event)
13{
14 // only handle F and composed F binds
15 if(((Event.m_Key >= KEY_F1 && Event.m_Key <= KEY_F12) || (Event.m_Key >= KEY_F13 && Event.m_Key <= KEY_F24)) && (Event.m_Key != KEY_F5 || !m_pClient->m_Menus.IsActive()))
16 {
17 int Mask = CBinds::GetModifierMask(pInput: Input());
18
19 // Look for a composed bind
20 bool ret = false;
21 if(m_pBinds->m_aapKeyBindings[Mask][Event.m_Key])
22 {
23 m_pBinds->GetConsole()->ExecuteLineStroked(Stroke: Event.m_Flags & IInput::FLAG_PRESS, pStr: m_pBinds->m_aapKeyBindings[Mask][Event.m_Key]);
24 ret = true;
25 }
26 // Look for a non composed bind
27 if(!ret && m_pBinds->m_aapKeyBindings[0][Event.m_Key])
28 {
29 m_pBinds->GetConsole()->ExecuteLineStroked(Stroke: Event.m_Flags & IInput::FLAG_PRESS, pStr: m_pBinds->m_aapKeyBindings[0][Event.m_Key]);
30 ret = true;
31 }
32
33 return ret;
34 }
35
36 return false;
37}
38
39CBinds::CBinds()
40{
41 mem_zero(block: m_aapKeyBindings, size: sizeof(m_aapKeyBindings));
42 m_SpecialBinds.m_pBinds = this;
43}
44
45CBinds::~CBinds()
46{
47 for(int i = 0; i < KEY_LAST; i++)
48 for(auto &apKeyBinding : m_aapKeyBindings)
49 free(ptr: apKeyBinding[i]);
50}
51
52void CBinds::Bind(int KeyId, const char *pStr, bool FreeOnly, int ModifierCombination)
53{
54 if(KeyId < 0 || KeyId >= KEY_LAST)
55 return;
56
57 if(FreeOnly && Get(KeyId, ModifierCombination)[0])
58 return;
59
60 free(ptr: m_aapKeyBindings[ModifierCombination][KeyId]);
61 m_aapKeyBindings[ModifierCombination][KeyId] = 0;
62
63 // skip modifiers for +xxx binds
64 if(pStr[0] == '+')
65 ModifierCombination = 0;
66
67 char aBuf[256];
68 if(!pStr[0])
69 {
70 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "unbound %s%s (%d)", GetKeyBindModifiersName(ModifierCombination), Input()->KeyName(Key: KeyId), KeyId);
71 }
72 else
73 {
74 int Size = str_length(str: pStr) + 1;
75 m_aapKeyBindings[ModifierCombination][KeyId] = (char *)malloc(size: Size);
76 str_copy(dst: m_aapKeyBindings[ModifierCombination][KeyId], src: pStr, dst_size: Size);
77 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "bound %s%s (%d) = %s", GetKeyBindModifiersName(ModifierCombination), Input()->KeyName(Key: KeyId), KeyId, m_aapKeyBindings[ModifierCombination][KeyId]);
78 }
79 Console()->Print(Level: IConsole::OUTPUT_LEVEL_ADDINFO, pFrom: "binds", pStr: aBuf, PrintColor: gs_BindPrintColor);
80}
81
82int CBinds::GetModifierMask(IInput *pInput)
83{
84 int Mask = 0;
85 static const auto s_aModifierKeys = {
86 KEY_LSHIFT,
87 KEY_RSHIFT,
88 KEY_LCTRL,
89 KEY_RCTRL,
90 KEY_LALT,
91 KEY_RALT,
92 KEY_LGUI,
93 KEY_RGUI,
94 };
95 for(const auto Key : s_aModifierKeys)
96 {
97 if(pInput->KeyIsPressed(Key))
98 {
99 Mask |= GetModifierMaskOfKey(Key);
100 }
101 }
102
103 return Mask;
104}
105
106int CBinds::GetModifierMaskOfKey(int Key)
107{
108 switch(Key)
109 {
110 case KEY_LSHIFT:
111 case KEY_RSHIFT:
112 return 1 << CBinds::MODIFIER_SHIFT;
113 case KEY_LCTRL:
114 case KEY_RCTRL:
115 return 1 << CBinds::MODIFIER_CTRL;
116 case KEY_LALT:
117 case KEY_RALT:
118 return 1 << CBinds::MODIFIER_ALT;
119 case KEY_LGUI:
120 case KEY_RGUI:
121 return 1 << CBinds::MODIFIER_GUI;
122 default:
123 return 0;
124 }
125}
126
127bool CBinds::OnInput(const IInput::CEvent &Event)
128{
129 // don't handle invalid events
130 if(Event.m_Key <= KEY_FIRST || Event.m_Key >= KEY_LAST)
131 return false;
132
133 int Mask = GetModifierMask(pInput: Input());
134 int KeyModifierMask = GetModifierMaskOfKey(Key: Event.m_Key);
135 Mask &= ~KeyModifierMask;
136
137 bool ret = false;
138 const char *pKey = nullptr;
139
140 if(m_aapKeyBindings[Mask][Event.m_Key])
141 {
142 if(Event.m_Flags & IInput::FLAG_PRESS)
143 {
144 Console()->ExecuteLineStroked(Stroke: 1, pStr: m_aapKeyBindings[Mask][Event.m_Key]);
145 pKey = m_aapKeyBindings[Mask][Event.m_Key];
146 }
147 // Have to check for nullptr again because the previous execute can unbind itself
148 if(Event.m_Flags & IInput::FLAG_RELEASE && m_aapKeyBindings[Mask][Event.m_Key])
149 Console()->ExecuteLineStroked(Stroke: 0, pStr: m_aapKeyBindings[Mask][Event.m_Key]);
150 ret = true;
151 }
152
153 if(m_aapKeyBindings[0][Event.m_Key] && !ret)
154 {
155 // When ctrl+shift are pressed (ctrl+shift binds and also the hard-coded ctrl+shift+d, ctrl+shift+g, ctrl+shift+e), ignore other +xxx binds
156 if(Event.m_Flags & IInput::FLAG_PRESS && Mask != ((1 << MODIFIER_CTRL) | (1 << MODIFIER_SHIFT)) && Mask != ((1 << MODIFIER_GUI) | (1 << MODIFIER_SHIFT)))
157 {
158 Console()->ExecuteLineStroked(Stroke: 1, pStr: m_aapKeyBindings[0][Event.m_Key]);
159 pKey = m_aapKeyBindings[Mask][Event.m_Key];
160 }
161 // Have to check for nullptr again because the previous execute can unbind itself
162 if(Event.m_Flags & IInput::FLAG_RELEASE && m_aapKeyBindings[0][Event.m_Key])
163 Console()->ExecuteLineStroked(Stroke: 0, pStr: m_aapKeyBindings[0][Event.m_Key]);
164 ret = true;
165 }
166
167 if(g_Config.m_ClSubTickAiming && pKey)
168 {
169 if(str_comp(a: "+fire", b: pKey) == 0 || str_comp(a: "+hook", b: pKey) == 0)
170 {
171 m_MouseOnAction = true;
172 }
173 }
174
175 return ret;
176}
177
178void CBinds::UnbindAll()
179{
180 for(auto &apKeyBinding : m_aapKeyBindings)
181 {
182 for(auto &pKeyBinding : apKeyBinding)
183 {
184 free(ptr: pKeyBinding);
185 pKeyBinding = 0;
186 }
187 }
188}
189
190const char *CBinds::Get(int KeyId, int ModifierCombination)
191{
192 if(KeyId > 0 && KeyId < KEY_LAST && m_aapKeyBindings[ModifierCombination][KeyId])
193 return m_aapKeyBindings[ModifierCombination][KeyId];
194 return "";
195}
196
197void CBinds::GetKey(const char *pBindStr, char *pBuf, size_t BufSize)
198{
199 pBuf[0] = 0;
200 for(int Mod = 0; Mod < MODIFIER_COMBINATION_COUNT; Mod++)
201 {
202 for(int KeyId = 0; KeyId < KEY_LAST; KeyId++)
203 {
204 const char *pBind = Get(KeyId, ModifierCombination: Mod);
205 if(!pBind[0])
206 continue;
207
208 if(str_comp(a: pBind, b: pBindStr) == 0)
209 {
210 if(Mod)
211 str_format(buffer: pBuf, buffer_size: BufSize, format: "%s+%s", GetModifierName(Modifier: Mod), Input()->KeyName(Key: KeyId));
212 else
213 str_copy(dst: pBuf, src: Input()->KeyName(Key: KeyId), dst_size: BufSize);
214 return;
215 }
216 }
217 }
218}
219
220void CBinds::SetDefaults()
221{
222 // set default key bindings
223 UnbindAll();
224 Bind(KeyId: KEY_F1, pStr: "toggle_local_console");
225 Bind(KeyId: KEY_F2, pStr: "toggle_remote_console");
226 Bind(KeyId: KEY_TAB, pStr: "+scoreboard");
227 Bind(KeyId: KEY_EQUALS, pStr: "+statboard");
228 Bind(KeyId: KEY_F10, pStr: "screenshot");
229
230 Bind(KeyId: KEY_A, pStr: "+left");
231 Bind(KeyId: KEY_D, pStr: "+right");
232
233 Bind(KeyId: KEY_SPACE, pStr: "+jump");
234 Bind(KeyId: KEY_MOUSE_1, pStr: "+fire");
235 Bind(KeyId: KEY_MOUSE_2, pStr: "+hook");
236 Bind(KeyId: KEY_LSHIFT, pStr: "+emote");
237 Bind(KeyId: KEY_RETURN, pStr: "+show_chat; chat all");
238 Bind(KeyId: KEY_RIGHT, pStr: "spectate_next");
239 Bind(KeyId: KEY_LEFT, pStr: "spectate_previous");
240 Bind(KeyId: KEY_RSHIFT, pStr: "+spectate");
241
242 Bind(KeyId: KEY_1, pStr: "+weapon1");
243 Bind(KeyId: KEY_2, pStr: "+weapon2");
244 Bind(KeyId: KEY_3, pStr: "+weapon3");
245 Bind(KeyId: KEY_4, pStr: "+weapon4");
246 Bind(KeyId: KEY_5, pStr: "+weapon5");
247
248 Bind(KeyId: KEY_MOUSE_WHEEL_UP, pStr: "+prevweapon");
249 Bind(KeyId: KEY_MOUSE_WHEEL_DOWN, pStr: "+nextweapon");
250
251 Bind(KeyId: KEY_T, pStr: "+show_chat; chat all");
252 Bind(KeyId: KEY_Y, pStr: "+show_chat; chat team");
253 Bind(KeyId: KEY_U, pStr: "+show_chat");
254 Bind(KeyId: KEY_I, pStr: "+show_chat; chat all /c ");
255
256 Bind(KeyId: KEY_F3, pStr: "vote yes");
257 Bind(KeyId: KEY_F4, pStr: "vote no");
258
259 Bind(KeyId: KEY_K, pStr: "kill");
260 Bind(KeyId: KEY_Q, pStr: "say /pause");
261 Bind(KeyId: KEY_P, pStr: "say /pause");
262
263 // DDRace
264 g_Config.m_ClDDRaceBindsSet = 0;
265 SetDDRaceBinds(false);
266}
267
268void CBinds::OnConsoleInit()
269{
270 ConfigManager()->RegisterCallback(pfnFunc: ConfigSaveCallback, pUserData: this);
271
272 Console()->Register(pName: "bind", pParams: "s[key] ?r[command]", Flags: CFGFLAG_CLIENT, pfnFunc: ConBind, pUser: this, pHelp: "Bind key to execute a command or view keybindings");
273 Console()->Register(pName: "binds", pParams: "?s[key]", Flags: CFGFLAG_CLIENT, pfnFunc: ConBinds, pUser: this, pHelp: "Print command executed by this keybinding or all binds");
274 Console()->Register(pName: "unbind", pParams: "s[key]", Flags: CFGFLAG_CLIENT, pfnFunc: ConUnbind, pUser: this, pHelp: "Unbind key");
275 Console()->Register(pName: "unbindall", pParams: "", Flags: CFGFLAG_CLIENT, pfnFunc: ConUnbindAll, pUser: this, pHelp: "Unbind all keys");
276
277 // default bindings
278 SetDefaults();
279}
280
281void CBinds::ConBind(IConsole::IResult *pResult, void *pUserData)
282{
283 CBinds *pBinds = (CBinds *)pUserData;
284 const char *pBindStr = pResult->GetString(Index: 0);
285 int Modifier;
286 int KeyId = pBinds->GetBindSlot(pBindString: pBindStr, pModifierCombination: &Modifier);
287
288 if(!KeyId)
289 {
290 char aBuf[256];
291 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "key %s not found", pBindStr);
292 pBinds->Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "binds", pStr: aBuf, PrintColor: gs_BindPrintColor);
293 return;
294 }
295
296 if(pResult->NumArguments() == 1)
297 {
298 char aBuf[256];
299 const char *pKeyName = pResult->GetString(Index: 0);
300
301 if(!pBinds->m_aapKeyBindings[Modifier][KeyId])
302 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%s (%d) is not bound", pKeyName, KeyId);
303 else
304 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%s (%d) = %s", pKeyName, KeyId, pBinds->m_aapKeyBindings[Modifier][KeyId]);
305
306 pBinds->Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "binds", pStr: aBuf, PrintColor: gs_BindPrintColor);
307 return;
308 }
309
310 pBinds->Bind(KeyId, pStr: pResult->GetString(Index: 1), FreeOnly: false, ModifierCombination: Modifier);
311}
312
313void CBinds::ConBinds(IConsole::IResult *pResult, void *pUserData)
314{
315 CBinds *pBinds = (CBinds *)pUserData;
316 if(pResult->NumArguments() == 1)
317 {
318 char aBuf[256];
319 const char *pKeyName = pResult->GetString(Index: 0);
320
321 int Modifier;
322 int KeyId = pBinds->GetBindSlot(pBindString: pKeyName, pModifierCombination: &Modifier);
323 if(!KeyId)
324 {
325 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "key '%s' not found", pKeyName);
326 pBinds->Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "binds", pStr: aBuf, PrintColor: gs_BindPrintColor);
327 }
328 else
329 {
330 if(!pBinds->m_aapKeyBindings[Modifier][KeyId])
331 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%s (%d) is not bound", pKeyName, KeyId);
332 else
333 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%s (%d) = %s", pKeyName, KeyId, pBinds->m_aapKeyBindings[Modifier][KeyId]);
334
335 pBinds->Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "binds", pStr: aBuf, PrintColor: gs_BindPrintColor);
336 }
337 }
338 else if(pResult->NumArguments() == 0)
339 {
340 char aBuf[1024];
341 for(int i = 0; i < MODIFIER_COMBINATION_COUNT; i++)
342 {
343 for(int j = 0; j < KEY_LAST; j++)
344 {
345 if(!pBinds->m_aapKeyBindings[i][j])
346 continue;
347
348 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%s%s (%d) = %s", GetKeyBindModifiersName(ModifierCombination: i), pBinds->Input()->KeyName(Key: j), j, pBinds->m_aapKeyBindings[i][j]);
349 pBinds->Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "binds", pStr: aBuf, PrintColor: gs_BindPrintColor);
350 }
351 }
352 }
353}
354
355void CBinds::ConUnbind(IConsole::IResult *pResult, void *pUserData)
356{
357 CBinds *pBinds = (CBinds *)pUserData;
358 const char *pKeyName = pResult->GetString(Index: 0);
359 int Modifier;
360 int KeyId = pBinds->GetBindSlot(pBindString: pKeyName, pModifierCombination: &Modifier);
361
362 if(!KeyId)
363 {
364 char aBuf[256];
365 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "key %s not found", pKeyName);
366 pBinds->Console()->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "binds", pStr: aBuf, PrintColor: gs_BindPrintColor);
367 return;
368 }
369
370 pBinds->Bind(KeyId, pStr: "", FreeOnly: false, ModifierCombination: Modifier);
371}
372
373void CBinds::ConUnbindAll(IConsole::IResult *pResult, void *pUserData)
374{
375 CBinds *pBinds = (CBinds *)pUserData;
376 pBinds->UnbindAll();
377}
378
379int CBinds::GetKeyId(const char *pKeyName)
380{
381 // check for numeric
382 if(pKeyName[0] == '&')
383 {
384 int i = str_toint(str: pKeyName + 1);
385 if(i > 0 && i < KEY_LAST)
386 return i; // numeric
387 }
388
389 // search for key
390 for(int i = 0; i < KEY_LAST; i++)
391 {
392 if(str_comp_nocase(a: pKeyName, b: Input()->KeyName(Key: i)) == 0)
393 return i;
394 }
395
396 return 0;
397}
398
399int CBinds::GetBindSlot(const char *pBindString, int *pModifierCombination)
400{
401 *pModifierCombination = MODIFIER_NONE;
402 char aMod[32];
403 aMod[0] = '\0';
404 const char *pKey = str_next_token(str: pBindString, delim: "+", buffer: aMod, buffer_size: sizeof(aMod));
405 while(aMod[0] && *(pKey))
406 {
407 if(!str_comp_nocase(a: aMod, b: "shift"))
408 *pModifierCombination |= (1 << MODIFIER_SHIFT);
409 else if(!str_comp_nocase(a: aMod, b: "ctrl"))
410 *pModifierCombination |= (1 << MODIFIER_CTRL);
411 else if(!str_comp_nocase(a: aMod, b: "alt"))
412 *pModifierCombination |= (1 << MODIFIER_ALT);
413 else if(!str_comp_nocase(a: aMod, b: "gui"))
414 *pModifierCombination |= (1 << MODIFIER_GUI);
415 else
416 return 0;
417
418 if(str_find(haystack: pKey + 1, needle: "+"))
419 pKey = str_next_token(str: pKey + 1, delim: "+", buffer: aMod, buffer_size: sizeof(aMod));
420 else
421 break;
422 }
423 return GetKeyId(pKeyName: *pModifierCombination == MODIFIER_NONE ? aMod : pKey + 1);
424}
425
426const char *CBinds::GetModifierName(int Modifier)
427{
428 switch(Modifier)
429 {
430 case MODIFIER_SHIFT:
431 return "shift";
432 case MODIFIER_CTRL:
433 return "ctrl";
434 case MODIFIER_ALT:
435 return "alt";
436 case MODIFIER_GUI:
437 return "gui";
438 case MODIFIER_NONE:
439 default:
440 return "";
441 }
442}
443
444const char *CBinds::GetKeyBindModifiersName(int ModifierCombination)
445{
446 static char aModifier[256];
447 aModifier[0] = '\0';
448 for(int k = 1; k < MODIFIER_COUNT; k++)
449 {
450 if(ModifierCombination & (1 << k))
451 {
452 str_append(dst&: aModifier, src: GetModifierName(Modifier: k));
453 str_append(dst&: aModifier, src: "+");
454 }
455 }
456 return aModifier;
457}
458
459void CBinds::ConfigSaveCallback(IConfigManager *pConfigManager, void *pUserData)
460{
461 CBinds *pSelf = (CBinds *)pUserData;
462
463 pConfigManager->WriteLine(pLine: "unbindall");
464 for(int i = 0; i < MODIFIER_COMBINATION_COUNT; i++)
465 {
466 for(int j = 0; j < KEY_LAST; j++)
467 {
468 if(!pSelf->m_aapKeyBindings[i][j])
469 continue;
470
471 // worst case the str_escape can double the string length
472 int Size = str_length(str: pSelf->m_aapKeyBindings[i][j]) * 2 + 30;
473 char *pBuffer = (char *)malloc(size: Size);
474 char *pEnd = pBuffer + Size;
475
476 str_format(buffer: pBuffer, buffer_size: Size, format: "bind %s%s \"", GetKeyBindModifiersName(ModifierCombination: i), pSelf->Input()->KeyName(Key: j));
477 // process the string. we need to escape some characters
478 char *pDst = pBuffer + str_length(str: pBuffer);
479 str_escape(dst: &pDst, src: pSelf->m_aapKeyBindings[i][j], end: pEnd);
480 str_append(dst: pBuffer, src: "\"", dst_size: Size);
481
482 pConfigManager->WriteLine(pLine: pBuffer);
483 free(ptr: pBuffer);
484 }
485 }
486}
487
488// DDRace
489
490void CBinds::SetDDRaceBinds(bool FreeOnly)
491{
492 if(g_Config.m_ClDDRaceBindsSet < 1)
493 {
494 Bind(KeyId: KEY_KP_PLUS, pStr: "zoom+", FreeOnly);
495 Bind(KeyId: KEY_KP_MINUS, pStr: "zoom-", FreeOnly);
496 Bind(KeyId: KEY_KP_MULTIPLY, pStr: "zoom", FreeOnly);
497 Bind(KeyId: KEY_PAUSE, pStr: "say /pause", FreeOnly);
498 Bind(KeyId: KEY_UP, pStr: "+jump", FreeOnly);
499 Bind(KeyId: KEY_LEFT, pStr: "+left", FreeOnly);
500 Bind(KeyId: KEY_RIGHT, pStr: "+right", FreeOnly);
501 Bind(KeyId: KEY_LEFTBRACKET, pStr: "+prevweapon", FreeOnly);
502 Bind(KeyId: KEY_RIGHTBRACKET, pStr: "+nextweapon", FreeOnly);
503 Bind(KeyId: KEY_C, pStr: "say /rank", FreeOnly);
504 Bind(KeyId: KEY_V, pStr: "say /info", FreeOnly);
505 Bind(KeyId: KEY_B, pStr: "say /top5", FreeOnly);
506 Bind(KeyId: KEY_S, pStr: "+showhookcoll", FreeOnly);
507 Bind(KeyId: KEY_X, pStr: "toggle cl_dummy 0 1", FreeOnly);
508 Bind(KeyId: KEY_H, pStr: "toggle cl_dummy_hammer 0 1", FreeOnly);
509 Bind(KeyId: KEY_SLASH, pStr: "+show_chat; chat all /", FreeOnly);
510 Bind(KeyId: KEY_PAGEUP, pStr: "toggle cl_overlay_entities 0 100", FreeOnly);
511 Bind(KeyId: KEY_KP_0, pStr: "say /emote normal 999999", FreeOnly);
512 Bind(KeyId: KEY_KP_1, pStr: "say /emote happy 999999", FreeOnly);
513 Bind(KeyId: KEY_KP_2, pStr: "say /emote angry 999999", FreeOnly);
514 Bind(KeyId: KEY_KP_3, pStr: "say /emote pain 999999", FreeOnly);
515 Bind(KeyId: KEY_KP_4, pStr: "say /emote surprise 999999", FreeOnly);
516 Bind(KeyId: KEY_KP_5, pStr: "say /emote blink 999999", FreeOnly);
517 Bind(KeyId: KEY_MOUSE_3, pStr: "+spectate", FreeOnly);
518 Bind(KeyId: KEY_MINUS, pStr: "spectate_previous", FreeOnly);
519 Bind(KeyId: KEY_EQUALS, pStr: "spectate_next", FreeOnly);
520 }
521
522 g_Config.m_ClDDRaceBindsSet = 1;
523}
524