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