1 (*
2  * Hedgewars, a free turn based strategy game
3  * Copyright (c) 2004-2015 Andrey Korotaev <unC0Rr@gmail.com>
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; version 2 of the License
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17  *)
18 
19 {$INCLUDE "options.inc"}
20 
21 unit uInputHandler;
22 interface
23 uses SDLh, uTypes;
24 
25 procedure initModule;
26 procedure freeModule;
27 
KeyNameToCodenull28 function  KeyNameToCode(name: shortstring): LongInt; inline;
KeyNameToCodenull29 function  KeyNameToCode(name: shortstring; Modifier: shortstring): LongInt;
30 
KeyBindToCodenull31 function  KeyBindToCode(bind: shortstring): LongInt;
KeyBindToNamenull32 function  KeyBindToName(bind: shortstring): shortstring;
33 //procedure MaskModifier(var code: LongInt; modifier: LongWord);
34 procedure MaskModifier(Modifier: shortstring; var code: LongInt);
35 procedure ProcessMouseButton(event: TSDL_MouseButtonEvent; ButtonDown: boolean);
36 procedure ProcessMouseMotion(xrel, yrel: LongInt);
37 //procedure ProcessMouseWheel(x, y: LongInt);
38 procedure ProcessMouseWheel(y: LongInt);
39 procedure ProcessKey(event: TSDL_KeyboardEvent); inline;
40 procedure ProcessKey(code: LongInt; KeyDown: boolean);
41 
42 {$IFDEF USE_AM_NUMCOLUMN}
CheckDefaultSlotKeysnull43 function CheckDefaultSlotKeys: boolean;
44 {$ENDIF}
45 
46 procedure ResetKbd;
47 procedure ResetMouseWheel;
48 procedure FreezeEnterKey;
49 procedure InitKbdKeyTable;
50 
51 procedure SetBinds(var binds: TBinds);
52 procedure SetDefaultBinds;
53 procedure chDefaultBind(var id: shortstring);
54 procedure loadBinds(cmd, s: shortstring);
55 procedure addBind(var binds: TBinds; var id: shortstring);
56 
57 procedure ControllerInit;
58 procedure ControllerAxisEvent(joy, axis: Byte; value: Integer);
59 procedure ControllerHatEvent(joy, hat, value: Byte);
60 procedure ControllerButtonEvent(joy, button: Byte; pressed: Boolean);
61 
62 implementation
63 uses uKeyNames, uConsole, uCommands, uVariables, uConsts, uUtils, uDebug, uPhysFSLayer, uCursor;
64 
65 const
66     LSHIFT = $0200;
67     RSHIFT = $0400;
68     LALT   = $0800;
69     RALT   = $1000;
70     LCTRL  = $2000;
71     RCTRL  = $4000;
72 
73 var tkbd: array[0..cKbdMaxIndex] of boolean;
74     KeyNames: TKeyNames;
75     CurrentBinds: TBinds;
76     ControllerNumControllers: Integer;
77     ControllerEnabled: Integer;
78     ControllerNumAxes: array[0..5] of Integer;
79     //ControllerNumBalls: array[0..5] of Integer;
80     ControllerNumHats: array[0..5] of Integer;
81     ControllerNumButtons: array[0..5] of Integer;
82     //ControllerAxes: array[0..5] of array[0..19] of Integer;
83     //ControllerBalls: array[0..5] of array[0..19] of array[0..1] of Integer;
84     //ControllerHats: array[0..5] of array[0..19] of Byte;
85     //ControllerButtons: array[0..5] of array[0..19] of Byte;
86 
KeyNameToCodenull87 function  KeyNameToCode(name: shortstring): LongInt; inline;
88 begin
89     KeyNameToCode:= KeyNameToCode(name, '');
90 end;
91 
KeyNameToCodenull92 function KeyNameToCode(name: shortstring; Modifier: shortstring): LongInt;
93 var code: LongInt;
94 begin
95     name:= LowerCase(name);
96     code:= 0;
97     while (code <= cKeyMaxIndex) and (KeyNames[code] <> name) do inc(code);
98 
99     MaskModifier(Modifier, code);
100     KeyNameToCode:= code;
101 end;
102 
103 // Takes a control name (e.g. 'quit') and returns the corresponding key code,
104 // if it has been bound.
105 // Returns -1 if the control has not been bound.
KeyBindToCodenull106 function KeyBindToCode(bind: shortstring): LongInt;
107 var code, index: LongInt;
108 begin
109     index:= 0;
110     while (index <= High(CurrentBinds.binds)) and (CurrentBinds.binds[index] <> bind) do inc(index);
111     if index > High(CurrentBinds.binds) then
112         // Return error
113         KeyBindToCode:= -1
114     else begin
115         code:= 0;
116         while (code <= High(CurrentBinds.indices)) and (CurrentBinds.indices[code] <> index) do inc(code);
117         checkFails(code <= High(CurrentBinds.indices), 'Inconsistency in key binding registry', True);
118         KeyBindToCode:= code;
119     end;
120 end;
121 
122 // Takes a control name (e.g. 'quit') and returns the corresponding
123 // human-readable key name from SDL.
124 // FIXME: Does not work 100% for all keys yet, but at least it no
125 //        longer hardcodes any key name.
126 // TODO: Localize
KeyBindToNamenull127 function KeyBindToName(bind: shortstring): shortstring;
128 var code: LongInt;
129     name: shortstring;
130 begin
131     code:= KeyBindToCode(bind);
132     if code = -1 then
133         KeyBindToName:= trmsg[sidUnknownKey]
134     else
135         begin
136         name:= SDL_GetKeyName(SDL_GetKeyFromScancode(code));
137         if (name = 'Escape') then
138             // Let's shorten the name "Escape" for the quit menu
139             KeyBindToName:= 'Esc'
140         else if (length(name) <> 0) then
141             KeyBindToName:= name
142         else
143             begin
144             WriteLnToConsole('Error: KeyBindToName('+bind+') failed to find SDL key name!');
145             KeyBindToName:= trmsg[sidUnknownKey];
146             end;
147         end;
148 end;
149 
150 (*
151 procedure MaskModifier(var code: LongInt; Modifier: LongWord);
152 begin
153     if(Modifier and KMOD_LSHIFT) <> 0 then code:= code or LSHIFT;
154     if(Modifier and KMOD_RSHIFT) <> 0 then code:= code or LSHIFT;
155     if(Modifier and KMOD_LALT) <> 0 then code:= code or LALT;
156     if(Modifier and KMOD_RALT) <> 0 then code:= code or LALT;
157     if(Modifier and KMOD_LCTRL) <> 0 then code:= code or LCTRL;
158     if(Modifier and KMOD_RCTRL) <> 0 then code:= code or LCTRL;
159 end;
160 *)
161 procedure MaskModifier(Modifier: shortstring; var code: LongInt);
162 var mod_ : shortstring = '';
163     ModifierCount, i: LongInt;
164 begin
165 if Modifier = '' then exit;
166 ModifierCount:= 0;
167 
168 for i:= 1 to Length(Modifier) do
169     if(Modifier[i] = ':') then inc(ModifierCount);
170 
171 SplitByChar(Modifier, mod_, ':');//remove the first mod: part
172 Modifier:= mod_;
173 for i:= 0 to ModifierCount do
174     begin
175     mod_:= '';
176     SplitByChar(Modifier, mod_, ':');
177     if (Modifier = 'lshift')                    then code:= code or LSHIFT;
178     if (Modifier = 'rshift')                    then code:= code or RSHIFT;
179     if (Modifier = 'lalt')                      then code:= code or LALT;
180     if (Modifier = 'ralt')                      then code:= code or RALT;
181     if (Modifier = 'lctrl') or (mod_ = 'lmeta') then code:= code or LCTRL;
182     if (Modifier = 'rctrl') or (mod_ = 'rmeta') then code:= code or RCTRL;
183     Modifier:= mod_;
184     end;
185 end;
186 
187 procedure ProcessKey(code: LongInt; KeyDown: boolean);
188 var
189     Trusted: boolean;
190     curBind, s: shortstring;
191     readyAborter: boolean;
192 begin
193 if not(tkbd[code] xor KeyDown) then exit;
194 tkbd[code]:= KeyDown;
195 
196 Trusted:= (CurrentTeam <> nil)
197           and (not CurrentTeam^.ExtDriven)
198           and (CurrentHedgehog^.BotLevel = 0);
199 // REVIEW OR FIXME
200 // ctrl/cmd + q to close engine and frontend - this seems like a bad idea, since we let people set arbitrary binds, and don't warn them of this.
201 // There's no confirmation at all
202 // ctrl/cmd + q to close engine and frontend
203 if(KeyDown and (code = SDLK_q)) then
204     begin
205 {$IFDEF DARWIN}
206     if tkbd[KeyNameToCode('left_meta')] or tkbd[KeyNameToCode('right_meta')] then
207 {$ELSE}
208     if tkbd[KeyNameToCode('left_ctrl')] or tkbd[KeyNameToCode('right_ctrl')] then
209 {$ENDIF}
210         ParseCommand('halt', true);
211     end;
212 
213 // ctrl/cmd + w to close engine
214 if(KeyDown and (code = SDLK_w)) then
215     begin
216 {$IFDEF DARWIN}
217     // on OS X it this is expected behaviour
218     if tkbd[KeyNameToCode('left_meta')] or tkbd[KeyNameToCode('right_meta')] then
219 {$ELSE}
220     // on other systems use this shortcut only if the keys are not bound to any command
221     if tkbd[KeyNameToCode('left_ctrl')] or tkbd[KeyNameToCode('right_ctrl')] then
222         if ((CurrentBinds.indices[KeyNameToCode('left_ctrl')] = 0) or
223             (CurrentBinds.indices[KeyNameToCode('right_ctrl')] = 0)) and
224             (CurrentBinds.indices[SDLK_w] = 0) then
225 {$ENDIF}
226         ParseCommand('forcequit', true);
227     end;
228 
229 if CurrentBinds.indices[code] > 0 then
230     begin
231     curBind:= CurrentBinds.binds[CurrentBinds.indices[code]];
232 
233     // Check if the keypress should end the ready phase.
234     // Camera movement keys are "safe" since its equivalent to moving the mouse,
235     // which also does not end the ready phase.
236     readyAborter:= (curBind <> '+cur_u') and (curBind <> '+cur_d') and (curBind <> '+cur_l') and (curBind <> '+cur_r');
237 
238     if (code < cKeyMaxIndex - 2) // means not mouse buttons
239         and KeyDown
240         and (not ((curBind = 'put')
241                   or (curBind = 'ammomenu')
242                   or (curBind = '+cur_u')
243                   or (curBind = '+cur_d')
244                   or (curBind = '+cur_l')
245                   or (curBind = '+cur_r')))
246         and (CurrentTeam <> nil)
247         and (not CurrentTeam^.ExtDriven)
248         then bShowAmmoMenu:= false;
249 
250     if KeyDown then
251         begin
252         Trusted:= Trusted and (not isPaused); //releasing keys during pause should be allowed on the other hand
253 
254         if curBind = 'switch' then
255             LocalMessage:= LocalMessage or gmSwitch
256         else if curBind = '+precise' then
257             begin
258             LocalMessage:= LocalMessage or gmPrecise;
259             updateVolumeDelta(true);
260             updateCursorMovementDelta(true, CursorMovementX, CursorMovementX);
261             updateCursorMovementDelta(true, CursorMovementY, CursorMovementY);
262             end;
263 
264         ParseCommand(curBind, Trusted);
265         // End ready phase
266         if (readyAborter) and (CurrentTeam <> nil) and (not CurrentTeam^.ExtDriven) and (ReadyTimeLeft > 1) then
267             ParseCommand('gencmd R', true)
268         end
269     else if (curBind[1] = '+') then
270         begin
271         if curBind = '+precise' then
272             begin
273             LocalMessage:= LocalMessage and (not gmPrecise);
274             updateVolumeDelta(false);
275             updateCursorMovementDelta(false, CursorMovementX, CursorMovementX);
276             updateCursorMovementDelta(false, CursorMovementY, CursorMovementY);
277             end;
278         s:= curBind;
279         s[1]:= '-';
280         ParseCommand(s, Trusted);
281         // End ready phase
282         if (readyAborter) and (CurrentTeam <> nil) and (not CurrentTeam^.ExtDriven) and (ReadyTimeLeft > 1) then
283             ParseCommand('gencmd R', true)
284         end
285     else
286         begin
287         if curBind = 'switch' then
288             LocalMessage:= LocalMessage and (not gmSwitch)
289         end
290     end
291 end;
292 
293 procedure ProcessKey(event: TSDL_KeyboardEvent); inline;
294 var code: LongInt;
295 begin
296     // TODO
297     code:= LongInt(event.keysym.scancode);
298     //writelntoconsole('[KEY] '+inttostr(code)+ ' -> ''' +KeyNames[code] + ''', type = '+inttostr(event.type_));
299     ProcessKey(code, event.type_ = SDL_KEYDOWN);
300 end;
301 
302 procedure ProcessMouseButton(event: TSDL_MouseButtonEvent; ButtonDown: boolean);
303 begin
304     //writelntoconsole('[MOUSE] '+inttostr(event.button));
305     case event.button of
306         SDL_BUTTON_LEFT:
307             ProcessKey(KeyNameToCode('mousel'), ButtonDown);
308         SDL_BUTTON_MIDDLE:
309             ProcessKey(KeyNameToCode('mousem'), ButtonDown);
310         SDL_BUTTON_RIGHT:
311             ProcessKey(KeyNameToCode('mouser'), ButtonDown);
312         SDL_BUTTON_X1:
313             ProcessKey(KeyNameToCode('mousex1'), ButtonDown);
314         SDL_BUTTON_X2:
315             ProcessKey(KeyNameToCode('mousex2'), ButtonDown);
316         end;
317 end;
318 
319 procedure ProcessMouseMotion(xrel, yrel: LongInt);
320 begin
321     uCursor.updatePositionDelta(xrel, yrel);
322 end;
323 
324 var mwheelupCode, mwheeldownCode: Integer;
325 
326 //procedure ProcessMouseWheel(x, y: LongInt);
327 procedure ProcessMouseWheel(y: LongInt);
328 begin
329     // we don't use
330     //writelntoconsole('[MOUSEWHEEL] '+inttostr(x)+', '+inttostr(y));
331     if y > 0 then
332         begin
333         // reset other direction
334         if tkbd[mwheeldownCode] then
335             ProcessKey(mwheeldownCode, false);
336         // trigger "button down" event
337         if (not tkbd[mwheelupCode]) then
338             ProcessKey(mwheelupCode, true);
339         end
340     else if y < 0 then
341         begin
342         // reset other direction
343         if tkbd[mwheelupCode] then
344             ProcessKey(mwheelupCode, false);
345         // trigger "button down" event
346         if (not tkbd[mwheeldownCode]) then
347             ProcessKey(mwheeldownCode, true);
348         end;
349 end;
350 
351 procedure ResetMouseWheel();
352 begin
353     if tkbd[mwheelupCode] then
354         ProcessKey(mwheelupCode, false);
355     if tkbd[mwheeldownCode] then
356         ProcessKey(mwheeldownCode, false);
357 end;
358 
359 procedure ResetKbd;
360 var t: LongInt;
361 begin
362 for t:= 0 to cKbdMaxIndex do
363     if tkbd[t] then
364         ProcessKey(t, False);
365 end;
366 
367 procedure RegisterBind(var binds: TBinds; key, value: shortstring);
368 var code: LongInt;
369 begin
370     checkFails(binds.lastIndex < 255, 'Too many key bindings', true);
371 
372     code:= KeyNameToCode(key);
373 
374     checkFails(code >= 0, 'unknown key', true);
375 
376     if binds.indices[code] > 0 then
377     begin
378         binds.binds[binds.indices[code]]:= value
379     end
380     else begin
381         inc(binds.lastIndex);
382         binds.indices[code]:= binds.lastIndex;
383         binds.binds[binds.indices[code]]:= value
384     end;
385 end;
386 
387 procedure InitDefaultBinds;
388 var i: Longword;
389 begin
390     RegisterBind(DefaultBinds, 'escape', 'quit');
391     RegisterBind(DefaultBinds, _S'`', 'history');
392     RegisterBind(DefaultBinds, 'delete', 'rotmask');
393     RegisterBind(DefaultBinds, 'home', 'rottags');
394     RegisterBind(DefaultBinds, _S'm', '+mission');
395     RegisterBind(DefaultBinds, _S'o', 'gearinfo');
396 
397     //numpad
398     RegisterBind(DefaultBinds, 'keypad_8', '+cur_u');
399     RegisterBind(DefaultBinds, 'keypad_6', '+cur_r');
400     RegisterBind(DefaultBinds, 'keypad_4', '+cur_l');
401     RegisterBind(DefaultBinds, 'keypad_2', '+cur_d');
402 
403     RegisterBind(DefaultBinds, _S'0', '+volup');
404     RegisterBind(DefaultBinds, _S'9', '+voldown');
405     RegisterBind(DefaultBinds, _S'8', 'mute');
406     RegisterBind(DefaultBinds, _S'c', 'capture');
407     RegisterBind(DefaultBinds, _S'r', 'record');
408     RegisterBind(DefaultBinds, _S'h', 'findhh');
409     RegisterBind(DefaultBinds, _S'p', 'pause');
410     RegisterBind(DefaultBinds, _S'f', '+speedup');
411     RegisterBind(DefaultBinds, _S't', 'chat');
412     RegisterBind(DefaultBinds, _S'u', 'chat team');
413     RegisterBind(DefaultBinds, _S'y', 'confirm');
414 
415     RegisterBind(DefaultBinds, 'mousem', 'zoomreset');
416     RegisterBind(DefaultBinds, 'wheelup', 'zoomin');
417     RegisterBind(DefaultBinds, 'wheeldown', 'zoomout');
418 
419     RegisterBind(DefaultBinds, 'f12', 'fullscr');
420 
421     for i:= 1 to 10 do RegisterBind(DefaultBinds, 'f'+IntToStr(i), 'slot '+char(48+i));
422     for i:= 1 to 5  do RegisterBind(DefaultBinds, IntToStr(i), 'timer '+IntToStr(i));
423     RegisterBind(DefaultBinds, _S'n', 'timer_u');
424 
425     RegisterBind(DefaultBinds, 'mousel', '/put');
426     RegisterBind(DefaultBinds, 'mouser', 'ammomenu');
427     RegisterBind(DefaultBinds, 'backspace', 'hjump');
428     RegisterBind(DefaultBinds, 'tab', 'switch');
429     RegisterBind(DefaultBinds, 'return', 'ljump');
430     RegisterBind(DefaultBinds, 'space', '+attack');
431     RegisterBind(DefaultBinds, 'up', '+up');
432     RegisterBind(DefaultBinds, 'down', '+down');
433     RegisterBind(DefaultBinds, 'left', '+left');
434     RegisterBind(DefaultBinds, 'right', '+right');
435     RegisterBind(DefaultBinds, 'left_shift', '+precise');
436 
437     loadBinds('dbind', cPathz[ptConfig] + '/settings.ini');
438 end;
439 
440 
441 procedure InitKbdKeyTable;
442 var i, j, k: LongInt;
443 begin
444     // Mouse buttons and mouse wheel
445     KeyNames[cKeyMaxIndex    ]:= 'mousel';
446     KeyNames[cKeyMaxIndex - 1]:= 'mousem';
447     KeyNames[cKeyMaxIndex - 2]:= 'mouser';
448     KeyNames[cKeyMaxIndex - 3]:= 'mousex1';
449     KeyNames[cKeyMaxIndex - 4]:= 'mousex2';
450     mwheelupCode:= cKeyMaxIndex - 5;
451     KeyNames[mwheelupCode]:= 'wheelup';
452     mwheeldownCode:= cKeyMaxIndex - 6;
453     KeyNames[mwheeldownCode]:= 'wheeldown';
454 
455     // Keyboard keys
456     uKeyNames.populateKeyNames(KeyNames);
457 
458     // get the size of keyboard array
459     SDL_GetKeyboardState(@k);
460 
461     // Controller(s)
462     for j:= 0 to Pred(ControllerNumControllers) do
463         begin
464         for i:= 0 to Pred(ControllerNumAxes[j]) do
465             begin
466             KeyNames[k + 0]:= 'j' + IntToStr(j) + 'a' + IntToStr(i) + 'u';
467             KeyNames[k + 1]:= 'j' + IntToStr(j) + 'a' + IntToStr(i) + 'd';
468             inc(k, 2);
469             end;
470         for i:= 0 to Pred(ControllerNumHats[j]) do
471             begin
472             KeyNames[k + 0]:= 'j' + IntToStr(j) + 'h' + IntToStr(i) + 'u';
473             KeyNames[k + 1]:= 'j' + IntToStr(j) + 'h' + IntToStr(i) + 'r';
474             KeyNames[k + 2]:= 'j' + IntToStr(j) + 'h' + IntToStr(i) + 'd';
475             KeyNames[k + 3]:= 'j' + IntToStr(j) + 'h' + IntToStr(i) + 'l';
476             inc(k, 4);
477             end;
478         for i:= 0 to Pred(ControllerNumButtons[j]) do
479             begin
480             KeyNames[k]:= 'j' + IntToStr(j) + 'b' + IntToStr(i);
481             inc(k, 1);
482             end;
483         end;
484 
485         InitDefaultBinds
486 end;
487 
488 
489 {$IFDEF USE_AM_NUMCOLUMN}
CheckDefaultSlotKeysnull490 function CheckDefaultSlotKeys: boolean;
491 {$IFDEF USE_TOUCH_INTERFACE}
492 begin
493     CheckDefaultSlotKeys:= false;
494 {$ELSE}
495 var i, code: LongInt;
496 begin
497     for i:=1 to cMaxSlotIndex do
498         begin
499         code:= KeyNameToCode('f'+IntToStr(i));
500         if CurrentBinds.binds[CurrentBinds.indices[code]] <> 'slot '+char(i+48) then
501             begin
502             CheckDefaultSlotKeys:= false;
503             exit;
504             end;
505         end;
506     CheckDefaultSlotKeys:= true;
507 {$ENDIF}
508 end;
509 {$ENDIF}
510 
511 {$IFNDEF MOBILE}
512 procedure SetBinds(var binds: TBinds);
513 var
514     t: LongInt;
515 begin
516     for t:= 0 to cKbdMaxIndex do
517         if (CurrentBinds.binds[CurrentBinds.indices[t]] <> binds.binds[binds.indices[t]]) and tkbd[t] then
518             ProcessKey(t, False);
519 
520     CurrentBinds:= binds;
521 end;
522 {$ELSE}
523 procedure SetBinds(var binds: TBinds);
524 begin
525     binds:= binds; // avoid hint
526     CurrentBinds:= DefaultBinds;
527 end;
528 {$ENDIF}
529 
530 procedure SetDefaultBinds;
531 begin
532     CurrentBinds:= DefaultBinds;
533 end;
534 
535 procedure FreezeEnterKey;
536 begin
537     tkbd[3]:= True;
538     tkbd[13]:= True;
539     tkbd[27]:= True;
540     tkbd[271]:= True;
541 end;
542 
543 var Controller: array [0..5] of PSDL_Joystick;
544 
545 procedure ControllerInit;
546 var j: Integer;
547 begin
548 ControllerEnabled:= 0;
549 {$IFDEF IPHONEOS}
550 exit; // joystick subsystem disabled on iPhone
551 {$ENDIF}
552 
553 SDL_InitSubSystem(SDL_INIT_JOYSTICK);
554 ControllerNumControllers:= SDL_NumJoysticks();
555 
556 if ControllerNumControllers > 6 then
557     ControllerNumControllers:= 6;
558 
559 WriteLnToConsole('Number of game controllers: ' + IntToStr(ControllerNumControllers));
560 
561 if ControllerNumControllers > 0 then
562     begin
563     for j:= 0 to pred(ControllerNumControllers) do
564         begin
565         WriteLnToConsole('Game controller no. ' + IntToStr(j) + ', name "' + shortstring(SDL_JoystickNameForIndex(j)) + '":');
566         Controller[j]:= SDL_JoystickOpen(j);
567         if Controller[j] = nil then
568             WriteLnToConsole('* Failed to open game controller no. ' + IntToStr(j) + '!')
569         else
570             begin
571             ControllerNumAxes[j]:= SDL_JoystickNumAxes(Controller[j]);
572             //ControllerNumBalls[j]:= SDL_JoystickNumBalls(Controller[j]);
573             ControllerNumHats[j]:= SDL_JoystickNumHats(Controller[j]);
574             ControllerNumButtons[j]:= SDL_JoystickNumButtons(Controller[j]);
575             WriteLnToConsole('* Number of axes: ' + IntToStr(ControllerNumAxes[j]));
576             //WriteLnToConsole('* Number of balls: ' + IntToStr(ControllerNumBalls[j]));
577             WriteLnToConsole('* Number of hats: ' + IntToStr(ControllerNumHats[j]));
578             WriteLnToConsole('* Number of buttons: ' + IntToStr(ControllerNumButtons[j]));
579             ControllerEnabled:= 1;
580 
581             if ControllerNumAxes[j] > 20 then
582                 ControllerNumAxes[j]:= 20;
583             //if ControllerNumBalls[j] > 20 then ControllerNumBalls[j]:= 20;
584 
585             if ControllerNumHats[j] > 20 then
586                 ControllerNumHats[j]:= 20;
587 
588             if ControllerNumButtons[j] > 20 then
589                 ControllerNumButtons[j]:= 20;
590 
591             (*// reset all buttons/axes
592             for i:= 0 to pred(ControllerNumAxes[j]) do
593                 ControllerAxes[j][i]:= 0;
594             for i:= 0 to pred(ControllerNumBalls[j]) do
595                 begin
596                 ControllerBalls[j][i][0]:= 0;
597                 ControllerBalls[j][i][1]:= 0;
598                 end;
599             for i:= 0 to pred(ControllerNumHats[j]) do
600                 ControllerHats[j][i]:= SDL_HAT_CENTERED;
601             for i:= 0 to pred(ControllerNumButtons[j]) do
602                 ControllerButtons[j][i]:= 0;*)
603             end;
604         end;
605     // enable event generation/controller updating
606     SDL_JoystickEventState(1);
607     end
608 else
609     WriteLnToConsole('Not using any game controller');
610 end;
611 
612 procedure ControllerAxisEvent(joy, axis: Byte; value: Integer);
613 var
614     k: LongInt;
615 begin
616     SDL_GetKeyboardState(@k);
617     k:= k + joy * (ControllerNumAxes[joy]*2 + ControllerNumHats[joy]*4 + ControllerNumButtons[joy]*2);
618     ProcessKey(k +  axis*2, value > 20000);
619     ProcessKey(k + (axis*2)+1, value < -20000);
620 end;
621 
622 procedure ControllerHatEvent(joy, hat, value: Byte);
623 var
624     k: LongInt;
625 begin
626     SDL_GetKeyboardState(@k);
627     k:= k + joy * (ControllerNumAxes[joy]*2 + ControllerNumHats[joy]*4 + ControllerNumButtons[joy]*2);
628     ProcessKey(k +  ControllerNumAxes[joy]*2 + hat*4 + 0, (value and SDL_HAT_UP)   <> 0);
629     ProcessKey(k +  ControllerNumAxes[joy]*2 + hat*4 + 1, (value and SDL_HAT_RIGHT)<> 0);
630     ProcessKey(k +  ControllerNumAxes[joy]*2 + hat*4 + 2, (value and SDL_HAT_DOWN) <> 0);
631     ProcessKey(k +  ControllerNumAxes[joy]*2 + hat*4 + 3, (value and SDL_HAT_LEFT) <> 0);
632 end;
633 
634 procedure ControllerButtonEvent(joy, button: Byte; pressed: Boolean);
635 var
636     k: LongInt;
637 begin
638     SDL_GetKeyboardState(@k);
639     k:= k + joy * (ControllerNumAxes[joy]*2 + ControllerNumHats[joy]*4 + ControllerNumButtons[joy]*2);
640     ProcessKey(k +  ControllerNumAxes[joy]*2 + ControllerNumHats[joy]*4 + button, pressed);
641 end;
642 
643 procedure loadBinds(cmd, s: shortstring);
644 var i: LongInt;
645     f: PFSFile;
646     p, l: shortstring;
647     b: byte;
648 begin
649     if cOnlyStats then exit;
650 
651     AddFileLog('[BINDS] Loading binds from: ' + s);
652 
653     l:= '';
654     if pfsExists(s) then
655         begin
656         f:= pfsOpenRead(s);
657         while (not pfsEOF(f)) and (l <> '[Binds]') do
658             pfsReadLn(f, l);
659 
660         while (not pfsEOF(f)) and (l <> '') do
661             begin
662             pfsReadLn(f, l);
663 
664             p:= '';
665             i:= 1;
666             while (i <= length(l)) and (l[i] <> '=') do
667                 begin
668                 if l[i] = '%' then
669                     begin
670                     l[i]:= '$';
671                     val(copy(l, i, 3), b);
672                     p:= p + char(b);
673                     inc(i, 3)
674                     end
675                 else
676                     begin
677                     p:= p + l[i];
678                     inc(i)
679                     end;
680                 end;
681 
682             if i < length(l) then
683                 begin
684                 l:= copy(l, i + 1, length(l) - i);
685                 if l <> 'default' then
686                     begin
687                     if (length(l) = 2) and (l[1] = '\') then
688                         l:= l[1] + ''
689                     else if (l[1] = '"') and (l[length(l)] = '"') then
690                         l:= copy(l, 2, length(l) - 2);
691 
692                     p:= cmd + ' ' + l + ' ' + p;
693                     ParseCommand(p, true)
694                     end
695                 end
696             end;
697 
698         pfsClose(f)
699         end
700         else
701             AddFileLog('[BINDS] file not found');
702 end;
703 
704 
705 procedure addBind(var binds: TBinds; var id: shortstring);
706 var KeyName, Modifier, tmp: shortstring;
707     i, newCode, code, b: LongInt;
708 begin
709     KeyName:= '';
710     Modifier:= '';
711 
712     if(Pos('mod:', id) <> 0)then
713         begin
714         tmp:= '';
715         SplitBySpace(id, tmp);
716         Modifier:= id;
717         id:= tmp;
718         end;
719 
720     SplitBySpace(id, KeyName);
721     if KeyName[1]='"' then
722         Delete(KeyName, 1, 1);
723     if KeyName[byte(KeyName[0])]='"' then
724         Delete(KeyName, byte(KeyName[0]), 1);
725     b:= KeyNameToCode(id, Modifier);
726     if b = 0 then
727         OutError(errmsgUnknownVariable + ' "' + id + '"', false)
728     else
729     begin
730         // add bind: first check if this cmd is already bound, and remove old bind
731         i:= Low(binds.binds);
732         while (i <= High(binds.binds)) and (binds.binds[i] <> KeyName) do
733             inc(i);
734 
735         if (i <= High(binds.binds)) then
736         begin
737             code:= Low(binds.indices);
738             while (code <= High(binds.indices)) and (binds.indices[code] <> i) do
739                 inc(code);
740 
741             checkFails(code <= High(binds.indices), 'binds registry inconsistency', true);
742 
743             binds.indices[code]:= 0;
744             binds.binds[i]:= ''
745         end;
746 
747         if binds.indices[b] > 0 then
748             newCode:= binds.indices[b]
749         else if i >= High(binds.binds) then
750             begin
751                 inc(binds.lastIndex);
752                 checkFails(binds.lastIndex < High(binds.binds), 'too many binds', true);
753                 newCode:= binds.lastIndex
754             end else
755                 newCode:= i;
756 
757 
758     binds.indices[b]:= newCode;
759     binds.binds[binds.indices[b]]:= KeyName
760     end
761 end;
762 
763 // Bind that isn't a team bind, but overrides defaultbinds.
764 procedure chDefaultBind(var id: shortstring);
765 begin
766     addBind(DefaultBinds, id)
767 end;
768 
769 procedure initModule;
770 begin
771     // assign 0 until InitKbdKeyTable is called
772     mwheelupCode:= 0;
773     mwheeldownCode:= 0;
774 
775     RegisterVariable('dbind', @chDefaultBind, true );
776 end;
777 
778 procedure freeModule;
779 var j: LongInt;
780 begin
781     // close gamepad controllers
782     if ControllerEnabled > 0 then
783         for j:= 0 to pred(ControllerNumControllers) do
784             SDL_JoystickClose(Controller[j]);
785 end;
786 
787 end.
788