1 /*
2 
3 Copyright (C) 2015-2018 Night Dive Studios, LLC.
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, either version 3 of the License, or
8 (at your option) any later version.
9 
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU General Public License for more details.
14 
15 You should have received a copy of the GNU General Public License
16 along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 
18 */
19 //====================================================================================
20 //
21 //		System Shock - ©1994-1995 Looking Glass Technologies, Inc.
22 //
23 //		Prefs.c	-	Handles saving and loading preferences.
24 //                  Also loads and sets default keybinds.
25 //
26 //====================================================================================
27 
28 //--------------------
29 //  Includes
30 //--------------------
31 #include "Shock.h"
32 #include "Prefs.h"
33 
34 #include "popups.h"
35 #include "olhext.h"
36 #include "hotkey.h"
37 #include "input.h"
38 #include "mainloop.h"
39 #include "movekeys.h"
40 
41 //--------------------
42 //  Filenames
43 //--------------------
44 static const char *PREFS_FILENAME = "prefs.txt";
45 static const char *KEYBINDS_FILENAME = "keybinds.txt";
46 
47 //--------------------
48 //  Globals
49 //--------------------
50 ShockPrefs gShockPrefs;
51 char which_lang;
52 uchar sfx_on = TRUE;
53 
54 //--------------------
55 //  Externs
56 //--------------------
57 extern int _fr_global_detail;
58 extern bool DoubleSize;
59 extern bool SkipLines;
60 extern short mode_id;
61 
62 extern uchar curr_vol_lev;
63 extern uchar curr_sfx_vol;
64 extern uchar curr_alog_vol;
65 
66 extern uchar audiolog_setting;
67 
68 static const char *PREF_LANGUAGE     = "language";
69 static const char *PREF_CAPTUREMOUSE = "capture-mouse";
70 static const char *PREF_INVERTMOUSEY = "invert-mousey";
71 static const char *PREF_MUSIC_VOL    = "music-volume";
72 static const char *PREF_SFX_VOL      = "sfx-volume";
73 static const char *PREF_ALOG_VOL     = "alog-volume";
74 static const char *PREF_VIDEOMODE    = "video-mode";
75 static const char *PREF_HALFRES      = "half-resolution";
76 static const char *PREF_DETAIL       = "detail";
77 static const char *PREF_USE_OPENGL   = "use-opengl";
78 static const char *PREF_TEX_FILTER   = "texture-filter";
79 static const char *PREF_ONSCR_HELP   = "onscreen-help";
80 static const char *PREF_GAMMA        = "gamma";
81 static const char *PREF_MSG_LENGTH   = "message-length";
82 static const char *PREF_ALOG_SETTING = "alog-setting";
83 static const char *PREF_MIDI_BACKEND = "midi-backend";
84 static const char *PREF_MIDI_OUTPUT  = "midi-output";
85 
86 static void SetShockGlobals(void);
87 
88 //--------------------------------------------------------------------
89 //	  Initialize the preferences to their default settings.
90 //--------------------------------------------------------------------
SetDefaultPrefs(void)91 void SetDefaultPrefs(void) {
92 
93     gShockPrefs.prefVer = 0;
94     gShockPrefs.prefPlayIntro = 1;      // First time through, play the intro
95     gShockPrefs.goPopupLabels = true;
96     gShockPrefs.soBackMusic = true;
97 #ifdef USE_FLUIDSYNTH
98     gShockPrefs.soMidiBackend = 2; // default to fluidsynth when available
99 #else
100     gShockPrefs.soMidiBackend = 0; // default to adlmidi
101 #endif
102     gShockPrefs.soMidiOutput = 0;  // default to zero
103     gShockPrefs.soSoundFX = true;
104     gShockPrefs.doUseQD = false;
105 
106     //saved in prefs file
107 
108     gShockPrefs.goLanguage = 0;         // English
109     gShockPrefs.goCaptureMouse = true;
110     gShockPrefs.goInvertMouseY = false;
111     gShockPrefs.soMusicVolume = 75;
112     gShockPrefs.soSfxVolume = 100;
113     gShockPrefs.soAudioLogVolume = 100;
114     gShockPrefs.doVideoMode = 3;
115     gShockPrefs.doResolution = 0;       // High-res.
116     gShockPrefs.doDetail = 3;           // Max detail.
117     gShockPrefs.doUseOpenGL = false;
118     gShockPrefs.doTextureFilter = 0;      // unfiltered
119     gShockPrefs.goOnScreenHelp = true;
120     gShockPrefs.doGamma = 29;           // Default gamma (29 out of 100).
121     gShockPrefs.goMsgLength = 0;        // Normal
122     audiolog_setting = 1;
123 
124     SetShockGlobals();
125 }
126 
GetPrefsPathFilename(void)127 static char *GetPrefsPathFilename(void)
128 {
129   static char filename[512];
130 
131   FILE *f = fopen(PREFS_FILENAME, "r");
132   if (f != NULL)
133   {
134     fclose(f);
135     strcpy(filename, PREFS_FILENAME);
136   }
137   else
138   {
139     char *p = SDL_GetPrefPath("Interrupt", "SystemShock");
140     snprintf(filename, sizeof(filename), "%s%s", p, PREFS_FILENAME);
141     SDL_free(p);
142   }
143 
144   return filename;
145 }
146 
trim(char * s)147 static char *trim(char *s) {
148     while (*s && isspace(*s))
149         s++;
150     char *c = &s[strlen(s) - 1];
151     while (c >= s && isspace(*c))
152         *(c--) = '\0';
153     return s;
154 }
155 
is_true(const char * s)156 static bool is_true(const char *s) {
157     return strcasecmp(s, "yes") == 0 || strcasecmp(s, "true") == 0 || strcmp(s, "1") == 0;
158 }
159 
160 //--------------------------------------------------------------------
161 //	  Locate the preferences file and load them to set our global pref settings.
162 //--------------------------------------------------------------------
LoadPrefs(void)163 int16_t LoadPrefs(void) {
164     FILE *f = fopen(GetPrefsPathFilename(), "r");
165     if (!f) {
166         // file can't be open, write default preferences
167         return SavePrefs();
168     }
169 
170     char line[64];
171     while (fgets(line, sizeof(line), f)) {
172         char *eq = strchr(line, '=');
173         if (!eq) continue;
174         *eq = '\0';
175 
176         const char *key = trim(line);
177         const char *value = trim(eq + 1);
178 
179         if (strcasecmp(key, PREF_LANGUAGE) == 0) {
180             int lang = atoi(value);
181             if (lang >= 0 && lang <= 2)
182                 gShockPrefs.goLanguage = lang;
183         } else if (strcasecmp(key, PREF_CAPTUREMOUSE) == 0) {
184             gShockPrefs.goCaptureMouse = is_true(value);
185         } else if (strcasecmp(key, PREF_INVERTMOUSEY) == 0) {
186             gShockPrefs.goInvertMouseY = is_true(value);
187         } else if (strcasecmp(key, PREF_MUSIC_VOL) == 0) {
188             int vol = atoi(value);
189             if (vol >= 0 && vol <= 100) {
190                 gShockPrefs.soBackMusic = vol > 0;
191                 gShockPrefs.soMusicVolume = vol;
192             }
193         } else if (strcasecmp(key, PREF_SFX_VOL) == 0) {
194             int vol = atoi(value);
195             if (vol >= 0 && vol <= 100) {
196                 gShockPrefs.soSoundFX = vol > 0;
197                 gShockPrefs.soSfxVolume = vol;
198             }
199         } else if (strcasecmp(key, PREF_ALOG_VOL) == 0) {
200             int vol = atoi(value);
201             if (vol >= 0 && vol <= 100)
202                 gShockPrefs.soAudioLogVolume = vol;
203         } else if (strcasecmp(key, PREF_VIDEOMODE) == 0) {
204             int mode = atoi(value);
205             if (mode >= 0 && mode <= 4)
206                 gShockPrefs.doVideoMode = mode;
207         } else if (strcasecmp(key, PREF_HALFRES) == 0) {
208             gShockPrefs.doResolution = is_true(value);
209         } else if (strcasecmp(key, PREF_DETAIL) == 0) {
210             int detail = atoi(value);
211             if (detail >= 0 && detail <= 3)
212                 gShockPrefs.doDetail = detail;
213         } else if (strcasecmp(key, PREF_USE_OPENGL) == 0) {
214             gShockPrefs.doUseOpenGL = is_true(value);
215         } else if (strcasecmp(key, PREF_TEX_FILTER) == 0) {
216             int mode = atoi(value);
217             if (mode >= 0 && mode <= 1)
218                 gShockPrefs.doTextureFilter = (short)mode;
219         } else if (strcasecmp(key, PREF_ONSCR_HELP) == 0) {
220             gShockPrefs.goOnScreenHelp = is_true(value);
221         } else if (strcasecmp(key, PREF_GAMMA) == 0) {
222             int gamma = atoi(value);
223             if (gamma < 10) gamma = 10;
224             if (gamma > 100) gamma = 100;
225             gShockPrefs.doGamma = gamma;
226         } else if (strcasecmp(key, PREF_MSG_LENGTH) == 0) {
227             int ml = atoi(value);
228             if (ml >= 0 && ml <= 1)
229                 gShockPrefs.goMsgLength = ml;
230         } else if (strcasecmp(key, PREF_ALOG_SETTING) == 0) {
231             int as = atoi(value);
232             if (as >= 0 && as <= 2)
233                 audiolog_setting = as;
234         } else if (strcasecmp(key, PREF_MIDI_BACKEND) == 0) {
235             int mb = atoi(value);
236             if (mb >= 0 && mb <= 2)
237                 gShockPrefs.soMidiBackend = (short)mb;
238         } else if (strcasecmp(key, PREF_MIDI_OUTPUT) == 0) {
239             int mo = atoi(value);
240             if (mo >= 0)
241                 gShockPrefs.soMidiOutput = (short)mo;
242         }
243     }
244 
245     fclose(f);
246     SetShockGlobals();
247     return 0;
248 }
249 
250 //--------------------------------------------------------------------
251 //	  Save global settings in the preferences file.
252 //--------------------------------------------------------------------
SavePrefs(void)253 int16_t SavePrefs(void) {
254     INFO("Saving preferences");
255 
256     FILE *f = fopen(GetPrefsPathFilename(), "w");
257     if (!f) {
258         printf("ERROR: Failed to open preferences file\n");
259         return -1;
260     }
261 
262     fprintf(f, "%s = %d\n", PREF_LANGUAGE, which_lang);
263     fprintf(f, "%s = %s\n", PREF_CAPTUREMOUSE, gShockPrefs.goCaptureMouse ? "yes" : "no");
264     fprintf(f, "%s = %s\n", PREF_INVERTMOUSEY, gShockPrefs.goInvertMouseY ? "yes" : "no");
265     fprintf(f, "%s = %d\n", PREF_MUSIC_VOL, curr_vol_lev);
266     fprintf(f, "%s = %d\n", PREF_SFX_VOL, sfx_on ? curr_sfx_vol : 0);
267     fprintf(f, "%s = %d\n", PREF_ALOG_VOL, curr_alog_vol);
268     fprintf(f, "%s = %d\n", PREF_VIDEOMODE, mode_id);
269     fprintf(f, "%s = %s\n", PREF_HALFRES, DoubleSize ? "yes" : "no");
270     fprintf(f, "%s = %d\n", PREF_DETAIL, _fr_global_detail);
271     fprintf(f, "%s = %s\n", PREF_USE_OPENGL, gShockPrefs.doUseOpenGL ? "yes" : "no");
272     fprintf(f, "%s = %d\n", PREF_TEX_FILTER, gShockPrefs.doTextureFilter);
273     fprintf(f, "%s = %s\n", PREF_ONSCR_HELP, gShockPrefs.goOnScreenHelp ? "yes" : "no");
274     fprintf(f, "%s = %d\n", PREF_GAMMA, gShockPrefs.doGamma);
275     fprintf(f, "%s = %d\n", PREF_MSG_LENGTH, gShockPrefs.goMsgLength);
276     fprintf(f, "%s = %d\n", PREF_ALOG_SETTING, audiolog_setting);
277     fprintf(f, "%s = %d\n", PREF_MIDI_BACKEND, gShockPrefs.soMidiBackend);
278     fprintf(f, "%s = %d\n", PREF_MIDI_OUTPUT, gShockPrefs.soMidiOutput);
279     fclose(f);
280     return 0;
281 }
282 
283 //--------------------------------------------------------------------
284 //  Set the corresponding Shock globals from the prefs structure.
285 //--------------------------------------------------------------------
SetShockGlobals(void)286 static void SetShockGlobals(void) {
287     popup_cursors = gShockPrefs.goPopupLabels;
288     olh_active = gShockPrefs.goOnScreenHelp;
289     which_lang = gShockPrefs.goLanguage;
290 
291     sfx_on = gShockPrefs.soSoundFX;
292     curr_vol_lev = gShockPrefs.soMusicVolume;
293     curr_sfx_vol = gShockPrefs.soSfxVolume;
294     curr_alog_vol = gShockPrefs.soAudioLogVolume;
295 
296     mode_id = gShockPrefs.doVideoMode;
297     DoubleSize = (gShockPrefs.doResolution == 1); // Set this True for low-res.
298     SkipLines = gShockPrefs.doUseQD;
299     _fr_global_detail = gShockPrefs.doDetail;
300 }
301 
302 //************************************************************************************
303 
304 //********
305 //Keybinds
306 //********
307 
308 
309 
310 //Note that Alt / Option (on Mac) modifier key won't work until it is implemented in sdl_events.c
311 
312 
313 
314 static struct {const char *s; int ch, code;} KeyName2ChCode[] =
315 {
316   { "backspace ",       8, 0x33 },
317   { "tab ",             9, 0x30 },
318   { "enter ",          13, 0x24 },
319   { "escape ",         27, 0x35 },
320   { "space ",          32, 0x31 },
321   { "1 ",              49, 0x12 },
322   { "exclamation ",    33, 0x12 },
323   { "2 ",              50, 0x13 },
324   { "atsign ",         64, 0x13 },
325   { "3 ",              51, 0x14 },
326   { "numbersign ",     35, 0x14 },
327   { "4 ",              52, 0x15 },
328   { "dollar ",         36, 0x15 },
329   { "5 ",              53, 0x17 },
330   { "percent ",        37, 0x17 },
331   { "6 ",              54, 0x16 },
332   { "caret ",          94, 0x16 },
333   { "7 ",              55, 0x1A },
334   { "ampersand ",      38, 0x1A },
335   { "8 ",              56, 0x1C },
336   { "asterisk ",       42, 0x1C },
337   { "9 ",              57, 0x19 },
338   { "lparenthesis ",   40, 0x19 },
339   { "0 ",              48, 0x1D },
340   { "rparenthesis ",   41, 0x1D },
341   { "equals ",         61, 0x18 },
342   { "plus ",           43, 0x18 },
343   { "comma ",          44, 0x2B },
344   { "lessthan ",       60, 0x2B },
345   { "minus ",          45, 0x1B },
346   { "underscore ",     95, 0x1B },
347   { "period ",         46, 0x2F },
348   { "greaterthan ",    62, 0x2F },
349   { "slash ",          47, 0x2C },
350   { "questionmark ",   63, 0x2C },
351   { "quote ",          39, 0x27 },
352   { "doublequote ",    34, 0x27 },
353   { "semicolon ",      59, 0x29 },
354   { "colon ",          58, 0x29 },
355   { "a ",              97, 0x00 },
356   { "b ",              98, 0x0B },
357   { "c ",              99, 0x08 },
358   { "d ",             100, 0x02 },
359   { "e ",             101, 0x0E },
360   { "f ",             102, 0x03 },
361   { "g ",             103, 0x05 },
362   { "h ",             104, 0x04 },
363   { "i ",             105, 0x22 },
364   { "j ",             106, 0x26 },
365   { "k ",             107, 0x28 },
366   { "l ",             108, 0x25 },
367   { "m ",             109, 0x2E },
368   { "n ",             110, 0x2D },
369   { "o ",             111, 0x1F },
370   { "p ",             112, 0x23 },
371   { "q ",             113, 0x0C },
372   { "r ",             114, 0x0F },
373   { "s ",             115, 0x01 },
374   { "t ",             116, 0x11 },
375   { "u ",             117, 0x20 },
376   { "v ",             118, 0x09 },
377   { "w ",             119, 0x0D },
378   { "x ",             120, 0x07 },
379   { "y ",             121, 0x10 },
380   { "z ",             122, 0x06 },
381   { "A ",              65, 0x00 },
382   { "B ",              66, 0x0B },
383   { "C ",              67, 0x08 },
384   { "D ",              68, 0x02 },
385   { "E ",              69, 0x0E },
386   { "F ",              70, 0x03 },
387   { "G ",              71, 0x05 },
388   { "H ",              72, 0x04 },
389   { "I ",              73, 0x22 },
390   { "J ",              74, 0x26 },
391   { "K ",              75, 0x28 },
392   { "L ",              76, 0x25 },
393   { "M ",              77, 0x2E },
394   { "N ",              78, 0x2D },
395   { "O ",              79, 0x1F },
396   { "P ",              80, 0x23 },
397   { "Q ",              81, 0x0C },
398   { "R ",              82, 0x0F },
399   { "S ",              83, 0x01 },
400   { "T ",              84, 0x11 },
401   { "U ",              85, 0x20 },
402   { "V ",              86, 0x09 },
403   { "W ",              87, 0x0D },
404   { "X ",              88, 0x07 },
405   { "Y ",              89, 0x10 },
406   { "Z ",              90, 0x06 },
407   { "lbracket ",       91, 0x21 },
408   { "lcurbrace ",     123, 0x21 },
409   { "backslash ",      92, 0x2A },
410   { "vertline ",      124, 0x2A },
411   { "rbracket ",       93, 0x1E },
412   { "rcurbrace ",     125, 0x1E },
413   { "backquote ",      96, 0x32 },
414   { "tilde ",         126, 0x32 },
415   { "delete ",        127, 0x33 },
416 
417   //use these invented "ascii" codes for hotkey system
418   //see sdl_events.c
419   { "f1 ",              128 +  0, 0x7A },
420   { "f2 ",              128 +  1, 0x78 },
421   { "f3 ",              128 +  2, 0x63 },
422   { "f4 ",              128 +  3, 0x76 },
423   { "f5 ",              128 +  4, 0x60 },
424   { "f6 ",              128 +  5, 0x61 },
425   { "f7 ",              128 +  6, 0x62 },
426   { "f8 ",              128 +  7, 0x64 },
427   { "f9 ",              128 +  8, 0x65 },
428   { "f10 ",             128 +  9, 0x6D },
429   { "f11 ",             128 + 10, 0x67 },
430   { "f12 ",             128 + 11, 0x6F },
431   { "keypad_divide ",   128 + 12, 0x4B },
432   { "keypad_multiply ", 128 + 13, 0x43 },
433   { "keypad_minus ",    128 + 14, 0x4E },
434   { "keypad_plus ",     128 + 15, 0x45 },
435   { "keypad_enter ",    128 + 16, 0x4C },
436   { "keypad_decimal ",  128 + 17, 0x41 },
437   { "keypad_0 ",        128 + 18, 0x52 },
438 
439   //these have no invented "ascii" codes so they can't be used as hotkeys, only move keys
440   { "keypad_home ",     0, 0x59 },
441   { "keypad_up ",       0, 0x5B },
442   { "keypad_pgup ",     0, 0x5C },
443   { "keypad_left ",     0, 0x56 },
444   { "keypad_5 ",        0, 0x57 },
445   { "keypad_right ",    0, 0x58 },
446   { "keypad_end ",      0, 0x53 },
447   { "keypad_down ",     0, 0x54 },
448   { "keypad_pgdn ",     0, 0x55 },
449   { "home ",            0, 0x73 },
450   { "up ",              0, 0x7E },
451   { "pageup ",          0, 0x74 },
452   { "left ",            0, 0x7B },
453   { "right ",           0, 0x7C },
454   { "end ",             0, 0x77 },
455   { "down ",            0, 0x7D },
456   { "pagedown ",        0, 0x79 },
457 
458   { NULL,               0,    0 }
459 };
460 
461 
462 
463 //lower cases all characters in string p
464 //also converts tabs to spaces
LowerCaseInPlace(char * p)465 static void LowerCaseInPlace(char *p)
466 {
467   while (*p)
468   {
469     if (*p >= 'A' && *p <= 'Z') *p = *p - 'A' + 'a'; //convert upper to lower case
470     if (*p == '\t') *p = ' '; //convert tab to space
471     p++;
472   }
473 }
474 
475 
476 
477 //*********************************
478 //Set hotkey keybinds (see input.c)
479 //Also handles fire keybinds
480 //*********************************
481 
482 
483 
484 #ifdef AUDIOLOGS
485 extern uchar audiolog_cancel_func(ushort keycode, uint32_t context, intptr_t data);
486 #endif
487 extern uchar posture_hotkey_func(ushort keycode, uint32_t context, intptr_t data);
488 extern uchar toggle_mouse_look(ushort keycode, uint32_t context, intptr_t data);
489 extern uchar change_mode_func(ushort keycode, uint32_t context, intptr_t data);
490 extern uchar clear_fullscreen_func(ushort keycode, uint32_t context, intptr_t data);
491 extern uchar saveload_hotkey_func(ushort keycode, uint32_t context, intptr_t data);
492 extern uchar pause_game_func(ushort keycode, uint32_t context, intptr_t data);
493 extern uchar reload_weapon_hotkey(ushort keycode, uint32_t context, intptr_t data);
494 extern uchar select_grenade_hotkey(ushort keycode, uint32_t context, intptr_t data);
495 extern uchar toggle_olh_func(ushort keycode, uint32_t context, intptr_t data);
496 extern uchar select_drug_hotkey(ushort keycode, uint32_t context, intptr_t data);
497 extern uchar toggle_music_func(ushort keycode, uint32_t context, intptr_t data);
498 extern uchar demo_quit_func(ushort keycode, uint32_t context, intptr_t data);
499 extern uchar cycle_weapons_func(ushort keycode, uint32_t context, intptr_t data);
500 extern uchar MacDetailFunc(ushort keycode, uint32_t context, intptr_t data);
501 extern uchar toggle_opengl_func(ushort keycode, uint32_t context, intptr_t data);
502 extern uchar arm_grenade_hotkey(ushort keycode, uint32_t context, intptr_t data);
503 extern uchar use_drug_hotkey(ushort keycode, uint32_t context, intptr_t data);
504 extern uchar hud_color_bank_cycle(ushort keycode, uint32_t context, intptr_t data);
505 extern uchar olh_overlay_func(ushort keycode, uint32_t context, intptr_t data);
506 extern uchar keypad_hotkey_func(ushort keycode, uint32_t context, intptr_t data);
507 extern uchar MacHelpFunc(ushort keycode, uint32_t context, intptr_t data);
508 extern uchar wrapper_options_func(ushort keycode, uint32_t context, intptr_t data);
509 extern uchar toggle_giveall_func(ushort keycode, uint32_t context, intptr_t data);
510 extern uchar toggle_physics_func(ushort keycode, uint32_t context, intptr_t data);
511 extern uchar toggle_up_level_func(ushort keycode, uint32_t context, intptr_t data);
512 extern uchar toggle_down_level_func(ushort keycode, uint32_t context, intptr_t data);
513 
514 
515 
516 #define TAB_KEY    (KEY_TAB | KB_FLAG_DOWN)
517 #define S_TAB_KEY  (KEY_TAB | KB_FLAG_DOWN | KB_FLAG_SHIFT)
518 
519 #define DOWN(x)    ((x) | KB_FLAG_DOWN)
520 #define SHIFT(x)   (DOWN(x) | KB_FLAG_SHIFT)
521 #define CTRL(x)    (DOWN(x) | KB_FLAG_CTRL)
522 #define ALT(x)     (DOWN(x) | KB_FLAG_ALT)
523 
524 
525 
526 typedef struct HOTKEYLOOKUP_STRUCT
527 {
528   const char *s; intptr_t contexts; hotkey_callback func; intptr_t state; bool used; int def1, def2;
529 } HOTKEYLOOKUP;
530 
531 
532 
533 HOTKEYLOOKUP HotKeyLookup[] =
534 {
535 //  name                    contexts      func                    state           used  default key 1,2
536 #ifdef AUDIOLOGS
537   { "\"audiolog_cancel\"",  DEMO_CONTEXT, audiolog_cancel_func,   0                        , 0, CTRL('.'),     0 },
538 #endif
539   { "\"stand\"",            DEMO_CONTEXT, posture_hotkey_func,    0                        , 0, DOWN('t'), SHIFT('t') },
540   { "\"crouch\"",           DEMO_CONTEXT, posture_hotkey_func,    1                        , 0, DOWN('g'), SHIFT('g') },
541   { "\"prone\"",            DEMO_CONTEXT, posture_hotkey_func,    2                        , 0, DOWN('b'), SHIFT('b') },
542   { "\"toggle_freelook\"",  DEMO_CONTEXT, toggle_mouse_look,      TRUE                     , 0, DOWN('f'),     0 },
543   { "\"full_view\"",        DEMO_CONTEXT, change_mode_func,       FULLSCREEN_LOOP          , 0, CTRL('f'),     0 },
544   { "\"normal_view\"",      DEMO_CONTEXT, change_mode_func,       GAME_LOOP                , 0, CTRL('d'),     0 },
545   { "\"map_view\"",         DEMO_CONTEXT, change_mode_func,       AUTOMAP_LOOP             , 0, CTRL('a'),     0 },
546   { "\"clear_fullscreen\"", DEMO_CONTEXT, clear_fullscreen_func,  0                        , 0, DOWN(KEY_BS),  0 },
547   { "\"save_game\"",        DEMO_CONTEXT, saveload_hotkey_func,   FALSE                    , 0, CTRL('s'),     0 },
548   { "\"load_game\"",        DEMO_CONTEXT, saveload_hotkey_func,   TRUE                     , 0, CTRL('l'),     0 },
549   { "\"pause\"",            DEMO_CONTEXT, pause_game_func,        TRUE                     , 0, DOWN('p'),     0 },
550   { "\"reload_weapon 1\"",  DEMO_CONTEXT, reload_weapon_hotkey,   1                        , 0, CTRL(KEY_BS),  0 },
551   { "\"reload_weapon 0\"",  DEMO_CONTEXT, reload_weapon_hotkey,   0                        , 0, ALT(KEY_BS),   0 },
552   { "\"select_grenade\"",   DEMO_CONTEXT, select_grenade_hotkey,  0                        , 0, CTRL('\''),    0 },
553   { "\"toggle_olh\"",       DEMO_CONTEXT, toggle_olh_func,        0                        , 0, CTRL('h'),     0 },
554   { "\"select_drug\"",      DEMO_CONTEXT, select_drug_hotkey,     0                        , 0, CTRL(';'),     0 },
555   { "\"toggle_music\"",     DEMO_CONTEXT, toggle_music_func,      0                        , 0, CTRL('m'),     0 },
556   { "\"quit\"",             DEMO_CONTEXT, demo_quit_func,         0                        , 0, CTRL('q'),     0 },
557   { "\"cycle_weapons 1\"",  DEMO_CONTEXT, cycle_weapons_func,     1                        , 0, TAB_KEY,       0 },
558   { "\"cycle_weapons -1\"", DEMO_CONTEXT, cycle_weapons_func,     -1                       , 0, S_TAB_KEY,     0 },
559   { "\"cycle_detail\"",     DEMO_CONTEXT, MacDetailFunc,          0                        , 0, CTRL('1'),     0 },
560   { "\"toggle_opengl\"",   EVERY_CONTEXT, toggle_opengl_func,     0                        , 0, CTRL('g'),     0 },
561   { "\"arm_grenade\"",      DEMO_CONTEXT, arm_grenade_hotkey,     0                        , 0, ALT('\''),     0 },
562   { "\"use_drug\"",         DEMO_CONTEXT, use_drug_hotkey,        0                        , 0, ALT(';'),      0 },
563   { "\"hud_color\"",        DEMO_CONTEXT, hud_color_bank_cycle,   0                        , 0, ALT('h'),      0 },
564   { "\"showhelp\"",         DEMO_CONTEXT, olh_overlay_func,       (intptr_t)&olh_overlay_on, 0, ALT('o'),      0 },
565   { "\"keypad 0\"",         DEMO_CONTEXT, keypad_hotkey_func,     0                        , 0, DOWN('0'),     0 },
566   { "\"keypad 1\"",         DEMO_CONTEXT, keypad_hotkey_func,     0                        , 0, DOWN('1'),     0 },
567   { "\"keypad 2\"",         DEMO_CONTEXT, keypad_hotkey_func,     0                        , 0, DOWN('2'),     0 },
568   { "\"keypad 3\"",         DEMO_CONTEXT, keypad_hotkey_func,     0                        , 0, DOWN('3'),     0 },
569   { "\"keypad 4\"",         DEMO_CONTEXT, keypad_hotkey_func,     0                        , 0, DOWN('4'),     0 },
570   { "\"keypad 5\"",         DEMO_CONTEXT, keypad_hotkey_func,     0                        , 0, DOWN('5'),     0 },
571   { "\"keypad 6\"",         DEMO_CONTEXT, keypad_hotkey_func,     0                        , 0, DOWN('6'),     0 },
572   { "\"keypad 7\"",         DEMO_CONTEXT, keypad_hotkey_func,     0                        , 0, DOWN('7'),     0 },
573   { "\"keypad 8\"",         DEMO_CONTEXT, keypad_hotkey_func,     0                        , 0, DOWN('8'),     0 },
574   { "\"keypad 9\"",         DEMO_CONTEXT, keypad_hotkey_func,     0                        , 0, DOWN('9'),     0 },
575 //  { "\"mac_help\"",         DEMO_CONTEXT, MacHelpFunc,            0                        , 0, CTRL('/'),     0 },
576   { "\"toggle_options\"",   DEMO_CONTEXT, wrapper_options_func,   TRUE                     , 0, DOWN(KEY_ESC), 0 },
577   { "\"cheat_give_all\"",   DEMO_CONTEXT, toggle_giveall_func,    TRUE                     , 0, CTRL('2'),     0 },
578   { "\"cheat_physics\"",    DEMO_CONTEXT, toggle_physics_func,    TRUE                     , 0, CTRL('3'),     0 },
579   { "\"cheat_up_level\"",   DEMO_CONTEXT, toggle_up_level_func,   TRUE                     , 0, CTRL('4'),     0 },
580   { "\"cheat_down_level\"", DEMO_CONTEXT, toggle_down_level_func, TRUE                     , 0, CTRL('5'),     0 },
581 
582   { NULL, 0, 0, 0 }
583 };
584 
585 
586 
587 //ought to be enough for anybody
588 #define MAX_FIRE_KEYS  16
589 
590 int FireKeys[MAX_FIRE_KEYS+1]; //see input.c
591 
592 
593 
GetKeybindsPathFilename(void)594 static char *GetKeybindsPathFilename(void)
595 {
596   static char filename[512];
597 
598   FILE *f = fopen(KEYBINDS_FILENAME, "r");
599   if (f != NULL)
600   {
601     fclose(f);
602     strcpy(filename, KEYBINDS_FILENAME);
603   }
604   else
605   {
606     char *p = SDL_GetPrefPath("Interrupt", "SystemShock");
607     snprintf(filename, sizeof(filename), "%s%s", p, KEYBINDS_FILENAME);
608     SDL_free(p);
609   }
610 
611   return filename;
612 }
613 
614 
615 
616 //all hotkey initialization and hotkey_add()s are done in this function
617 //also handles setting fire keybinds
LoadHotkeyKeybinds(void)618 void LoadHotkeyKeybinds(void)
619 {
620   FILE *f;
621   char temp[512], *p;
622   const char *string;
623   int len, i, flags, ch, fire_key_index = 0;
624 
625   hotkey_init(NUM_HOTKEYS);
626 
627   //clear hotkey used flags so we can tell which weren't specified in file
628   //later we can add default key chars for them
629   i = 0;
630   while (HotKeyLookup[i].s != NULL)
631   {
632     HotKeyLookup[i].used = FALSE;
633     i++;
634   }
635 
636   f = fopen(GetKeybindsPathFilename(), "r");
637   if (f)
638   {
639     //scan keybinds file line by line
640     while (fgets(temp, sizeof(temp), f))
641     {
642       LowerCaseInPlace(temp);
643       p = temp;
644 
645       while (*p && isspace(*p)) p++; //skip leading spaces
646 
647       string = "bind"; len = strlen(string);
648       if (strncmp(p, string, len)) continue;
649       p += len;
650 
651       while (*p && isspace(*p)) p++; //skip leading spaces
652 
653       flags = KB_FLAG_DOWN;
654       ch = 0;
655 
656       string = "shift+"; len = strlen(string);
657       if (!strncmp(p, string, len)) {p += len; flags |= KB_FLAG_SHIFT;}
658 
659       string = "ctrl+"; len = strlen(string);
660       if (!strncmp(p, string, len)) {p += len; flags |= KB_FLAG_CTRL;}
661 
662       string = "alt+"; len = strlen(string);
663       if (!strncmp(p, string, len)) {p += len; flags |= KB_FLAG_ALT;}
664 
665       //get ascii char from key name
666       i = 0;
667       while (KeyName2ChCode[i].s != NULL)
668       {
669         string = KeyName2ChCode[i].s; len = strlen(string);
670         if (!strncmp(p, string, len)) {p += len; ch = KeyName2ChCode[i].ch | flags; break;}
671         i++;
672       }
673       if (ch == 0) continue;
674 
675       while (*p && isspace(*p)) p++; //skip leading spaces
676 
677       //lookup and add specified hotkey info
678       i = 0;
679       while (HotKeyLookup[i].s != NULL)
680       {
681         string = HotKeyLookup[i].s; len = strlen(string);
682         if (!strncmp(p, string, len))
683         {
684           hotkey_add(ch, HotKeyLookup[i].contexts, HotKeyLookup[i].func, HotKeyLookup[i].state);
685           HotKeyLookup[i].used = TRUE;
686           break;
687         }
688         i++;
689       }
690 
691       //special case for fire keys
692       string = "\"fire\""; len = strlen(string);
693       if (!strncmp(p, string, len))
694       {
695         if (fire_key_index < MAX_FIRE_KEYS)
696           FireKeys[fire_key_index++] = (ch & ~KB_FLAG_DOWN);
697       }
698     }
699 
700     fclose(f);
701   }
702 
703   //add defaults for unused hotkeys
704   i = 0;
705   while (HotKeyLookup[i].s != NULL)
706   {
707     if (!HotKeyLookup[i].used)
708     {
709       //add default 1
710       ch = HotKeyLookup[i].def1;
711       if (ch) hotkey_add(ch, HotKeyLookup[i].contexts, HotKeyLookup[i].func, HotKeyLookup[i].state);
712 
713       //add default 2
714       ch = HotKeyLookup[i].def2;
715       if (ch) hotkey_add(ch, HotKeyLookup[i].contexts, HotKeyLookup[i].func, HotKeyLookup[i].state);
716     }
717     i++;
718   }
719 
720   //add default fire key if none were specified
721   if (fire_key_index == 0)
722     FireKeys[fire_key_index++] = KEY_ENTER;
723 
724   //signal end of fire key list
725   FireKeys[fire_key_index] = 0;
726 }
727 
728 
729 
730 //************************************************************************************
731 
732 
733 
734 //**********************************
735 //Set move keybinds (see movekeys.c)
736 //**********************************
737 
738 
739 
740 extern MOVE_KEYBIND      MoveKeybinds[MAX_MOVE_KEYBINDS+1];
741 extern MOVE_KEYBIND MoveCyberKeybinds[MAX_MOVE_KEYBINDS+1];
742 
743 static MOVE_KEYBIND MoveKeybindsDefault[] =
744 {
745   { CODE_W        | KB_FLAG_SHIFT, M_RUNFORWARD    },
746   { CODE_UP       | KB_FLAG_SHIFT, M_RUNFORWARD    },
747   { CODE_UP       | KB_FLAG_ALT  , M_RUNFORWARD    },
748   { CODE_KP_UP    | KB_FLAG_SHIFT, M_RUNFORWARD    },
749   { CODE_KP_UP    | KB_FLAG_ALT  , M_RUNFORWARD    },
750   { CODE_W                       , M_FORWARD       },
751   { CODE_UP                      , M_FORWARD       },
752   { CODE_KP_UP                   , M_FORWARD       },
753   { CODE_Z        | KB_FLAG_SHIFT, M_FASTTURNLEFT  },
754   { CODE_LEFT     | KB_FLAG_SHIFT, M_FASTTURNLEFT  },
755   { CODE_KP_LEFT  | KB_FLAG_SHIFT, M_FASTTURNLEFT  },
756   { CODE_Z                       , M_TURNLEFT      },
757   { CODE_LEFT                    , M_TURNLEFT      },
758   { CODE_KP_LEFT                 , M_TURNLEFT      },
759   { CODE_C        | KB_FLAG_SHIFT, M_FASTTURNRIGHT },
760   { CODE_RIGHT    | KB_FLAG_SHIFT, M_FASTTURNRIGHT },
761   { CODE_KP_RIGHT | KB_FLAG_SHIFT, M_FASTTURNRIGHT },
762   { CODE_C                       , M_TURNRIGHT     },
763   { CODE_RIGHT                   , M_TURNRIGHT     },
764   { CODE_KP_RIGHT                , M_TURNRIGHT     },
765   { CODE_S                       , M_BACK          },
766   { CODE_S        | KB_FLAG_SHIFT, M_BACK          },
767   { CODE_DOWN                    , M_BACK          },
768   { CODE_DOWN     | KB_FLAG_SHIFT, M_BACK          },
769   { CODE_DOWN     | KB_FLAG_ALT  , M_BACK          },
770   { CODE_KP_DOWN                 , M_BACK          },
771   { CODE_KP_DOWN  | KB_FLAG_SHIFT, M_BACK          },
772   { CODE_KP_DOWN  | KB_FLAG_ALT  , M_BACK          },
773   { CODE_A                       , M_SLIDELEFT     },
774   { CODE_A        | KB_FLAG_SHIFT, M_SLIDELEFT     },
775   { CODE_LEFT     | KB_FLAG_ALT  , M_SLIDELEFT     },
776   { CODE_KP_LEFT  | KB_FLAG_ALT  , M_SLIDELEFT     },
777   { CODE_KP_END                  , M_SLIDELEFT     },
778   { CODE_D                       , M_SLIDERIGHT    },
779   { CODE_D        | KB_FLAG_SHIFT, M_SLIDERIGHT    },
780   { CODE_RIGHT    | KB_FLAG_ALT  , M_SLIDERIGHT    },
781   { CODE_KP_RIGHT | KB_FLAG_ALT  , M_SLIDERIGHT    },
782   { CODE_KP_PGDN                 , M_SLIDERIGHT    },
783   { CODE_J                       , M_JUMP          },
784   { CODE_J        | KB_FLAG_SHIFT, M_JUMP          },
785   { CODE_SPACE                   , M_JUMP          },
786   { CODE_SPACE    | KB_FLAG_SHIFT, M_JUMP          },
787   { CODE_SPACE    | KB_FLAG_CTRL , M_JUMP          },
788   { CODE_SPACE    | KB_FLAG_ALT  , M_JUMP          },
789   { CODE_X                       , M_LEANUP        },
790   { CODE_X        | KB_FLAG_SHIFT, M_LEANUP        },
791   { CODE_X        | KB_FLAG_CTRL , M_LEANUP        },
792   { CODE_X        | KB_FLAG_ALT  , M_LEANUP        },
793   { CODE_Q                       , M_LEANLEFT      },
794   { CODE_Q        | KB_FLAG_SHIFT, M_LEANLEFT      },
795   { CODE_LEFT     | KB_FLAG_CTRL , M_LEANLEFT      },
796   { CODE_KP_LEFT  | KB_FLAG_CTRL , M_LEANLEFT      },
797   { CODE_E                       , M_LEANRIGHT     },
798   { CODE_E        | KB_FLAG_SHIFT, M_LEANRIGHT     },
799   { CODE_RIGHT    | KB_FLAG_CTRL , M_LEANRIGHT     },
800   { CODE_KP_RIGHT | KB_FLAG_CTRL , M_LEANRIGHT     },
801   { CODE_R                       , M_LOOKUP        },
802   { CODE_R        | KB_FLAG_SHIFT, M_LOOKUP        },
803   { CODE_R        | KB_FLAG_CTRL , M_LOOKUP        },
804   { CODE_UP       | KB_FLAG_CTRL , M_LOOKUP        },
805   { CODE_KP_UP    | KB_FLAG_CTRL , M_LOOKUP        },
806   { CODE_V                       , M_LOOKDOWN      },
807   { CODE_V        | KB_FLAG_SHIFT, M_LOOKDOWN      },
808   { CODE_V        | KB_FLAG_CTRL , M_LOOKDOWN      },
809   { CODE_DOWN     | KB_FLAG_CTRL , M_LOOKDOWN      },
810   { CODE_KP_DOWN  | KB_FLAG_CTRL , M_LOOKDOWN      },
811   { CODE_KP_HOME                 , M_RUNLEFT       },
812   { CODE_KP_PGUP                 , M_RUNRIGHT      },
813   { CODE_S                       , M_THRUST        }, //cyber start
814   { CODE_S        | KB_FLAG_SHIFT, M_THRUST        },
815   { CODE_KP_5                    , M_THRUST        },
816   { CODE_W                       , M_CLIMB         },
817   { CODE_W        | KB_FLAG_SHIFT, M_CLIMB         },
818   { CODE_UP                      , M_CLIMB         },
819   { CODE_UP       | KB_FLAG_SHIFT, M_CLIMB         },
820   { CODE_UP       | KB_FLAG_CTRL , M_CLIMB         },
821   { CODE_UP       | KB_FLAG_ALT  , M_CLIMB         },
822   { CODE_KP_UP                   , M_CLIMB         },
823   { CODE_KP_UP    | KB_FLAG_SHIFT, M_CLIMB         },
824   { CODE_KP_UP    | KB_FLAG_CTRL , M_CLIMB         },
825   { CODE_KP_UP    | KB_FLAG_ALT  , M_CLIMB         },
826   { CODE_A                       , M_BANKLEFT      },
827   { CODE_A        | KB_FLAG_SHIFT, M_BANKLEFT      },
828   { CODE_KP_LEFT                 , M_BANKLEFT      },
829   { CODE_KP_LEFT  | KB_FLAG_SHIFT, M_BANKLEFT      },
830   { CODE_KP_LEFT  | KB_FLAG_CTRL , M_BANKLEFT      },
831   { CODE_KP_LEFT  | KB_FLAG_ALT  , M_BANKLEFT      },
832   { CODE_D                       , M_BANKRIGHT     },
833   { CODE_D        | KB_FLAG_SHIFT, M_BANKRIGHT     },
834   { CODE_KP_RIGHT                , M_BANKRIGHT     },
835   { CODE_KP_RIGHT | KB_FLAG_SHIFT, M_BANKRIGHT     },
836   { CODE_KP_RIGHT | KB_FLAG_CTRL , M_BANKRIGHT     },
837   { CODE_KP_RIGHT | KB_FLAG_ALT  , M_BANKRIGHT     },
838   { CODE_X                       , M_DIVE          },
839   { CODE_X        | KB_FLAG_SHIFT, M_DIVE          },
840   { CODE_DOWN                    , M_DIVE          },
841   { CODE_DOWN     | KB_FLAG_SHIFT, M_DIVE          },
842   { CODE_DOWN     | KB_FLAG_CTRL , M_DIVE          },
843   { CODE_DOWN     | KB_FLAG_ALT  , M_DIVE          },
844   { CODE_KP_DOWN                 , M_DIVE          },
845   { CODE_KP_DOWN  | KB_FLAG_SHIFT, M_DIVE          },
846   { CODE_KP_DOWN  | KB_FLAG_CTRL , M_DIVE          },
847   { CODE_KP_DOWN  | KB_FLAG_ALT  , M_DIVE          },
848   { CODE_Q                       , M_ROLLRIGHT     },
849   { CODE_Q        | KB_FLAG_SHIFT, M_ROLLRIGHT     },
850   { CODE_Z                       , M_ROLLRIGHT     },
851   { CODE_Z        | KB_FLAG_SHIFT, M_ROLLRIGHT     },
852   { CODE_E                       , M_ROLLLEFT      },
853   { CODE_E        | KB_FLAG_SHIFT, M_ROLLLEFT      },
854   { CODE_C                       , M_ROLLLEFT      },
855   { CODE_C        | KB_FLAG_SHIFT, M_ROLLLEFT      },
856   { CODE_KP_HOME                 , M_CLIMBLEFT     },
857   { CODE_KP_HOME  | KB_FLAG_SHIFT, M_CLIMBLEFT     },
858   { CODE_KP_HOME  | KB_FLAG_CTRL , M_CLIMBLEFT     },
859   { CODE_KP_HOME  | KB_FLAG_ALT  , M_CLIMBLEFT     },
860   { CODE_KP_PGUP                 , M_CLIMBRIGHT    },
861   { CODE_KP_PGUP  | KB_FLAG_SHIFT, M_CLIMBRIGHT    },
862   { CODE_KP_PGUP  | KB_FLAG_CTRL , M_CLIMBRIGHT    },
863   { CODE_KP_PGUP  | KB_FLAG_ALT  , M_CLIMBRIGHT    },
864   { CODE_KP_PGDN                 , M_DIVERIGHT     },
865   { CODE_KP_PGDN  | KB_FLAG_SHIFT, M_DIVERIGHT     },
866   { CODE_KP_PGDN  | KB_FLAG_CTRL , M_DIVERIGHT     },
867   { CODE_KP_PGDN  | KB_FLAG_ALT  , M_DIVERIGHT     },
868   { CODE_KP_END                  , M_DIVELEFT      },
869   { CODE_KP_END   | KB_FLAG_SHIFT, M_DIVELEFT      },
870   { CODE_KP_END   | KB_FLAG_CTRL , M_DIVELEFT      },
871   { CODE_KP_END   | KB_FLAG_ALT  , M_DIVELEFT      },
872 
873   { 255                          , -1              }
874 };
875 
876 
877 
878 static struct {const char *s; int move;} MoveName2Move[] =
879 {
880   { "\"runforward\"",    M_RUNFORWARD    },
881   { "\"forward\"",       M_FORWARD       },
882   { "\"fastturnleft\"",  M_FASTTURNLEFT  },
883   { "\"turnleft\"",      M_TURNLEFT      },
884   { "\"fastturnright\"", M_FASTTURNRIGHT },
885   { "\"turnright\"",     M_TURNRIGHT     },
886   { "\"back\"",          M_BACK          },
887   { "\"slideleft\"",     M_SLIDELEFT     },
888   { "\"slideright\"",    M_SLIDERIGHT    },
889   { "\"jump\"",          M_JUMP          },
890   { "\"leanup\"",        M_LEANUP        },
891   { "\"leanleft\"",      M_LEANLEFT      },
892   { "\"leanright\"",     M_LEANRIGHT     },
893   { "\"lookup\"",        M_LOOKUP        },
894   { "\"lookdown\"",      M_LOOKDOWN      },
895   { "\"runleft\"",       M_RUNLEFT       },
896   { "\"runright\"",      M_RUNRIGHT      },
897   { "\"thrust\"",        M_THRUST        }, //cyber start
898   { "\"climb\"",         M_CLIMB         },
899   { "\"bankleft\"",      M_BANKLEFT      },
900   { "\"bankright\"",     M_BANKRIGHT     },
901   { "\"dive\"",          M_DIVE          },
902   { "\"rollright\"",     M_ROLLRIGHT     },
903   { "\"rollleft\"",      M_ROLLLEFT      },
904   { "\"climbleft\"",     M_CLIMBLEFT     },
905   { "\"climbright\"",    M_CLIMBRIGHT    },
906   { "\"diveright\"",     M_DIVERIGHT     },
907   { "\"diveleft\"",      M_DIVELEFT      },
908 
909   { NULL,                0               }
910 };
911 
912 
913 
LoadMoveKeybinds(void)914 void LoadMoveKeybinds(void)
915 {
916   FILE *f;
917   char temp[512], *p, move_used[NUM_MOVES];
918   const char *string;
919   int len, i, flags, code, move, num_bound = 0, num_cyber_bound = 0;
920 
921   //keep track of which moves are specified so we can add default ones for those that are missing
922   memset(move_used, 0, NUM_MOVES);
923 
924   f = fopen(GetKeybindsPathFilename(), "r");
925   if (f)
926   {
927     //scan keybinds file line by line
928     while (fgets(temp, sizeof(temp), f))
929     {
930       LowerCaseInPlace(temp);
931       p = temp;
932 
933       while (*p && isspace(*p)) p++; //skip leading spaces
934 
935       string = "bind"; len = strlen(string);
936       if (strncmp(p, string, len)) continue;
937       p += len;
938 
939       while (*p && isspace(*p)) p++; //skip leading spaces
940 
941       flags = 0;
942       code = 255;
943 
944       string = "shift+"; len = strlen(string);
945       if (!strncmp(p, string, len)) {p += len; flags |= KB_FLAG_SHIFT;}
946 
947       string = "ctrl+"; len = strlen(string);
948       if (!strncmp(p, string, len)) {p += len; flags |= KB_FLAG_CTRL;}
949 
950       string = "alt+"; len = strlen(string);
951       if (!strncmp(p, string, len)) {p += len; flags |= KB_FLAG_ALT;}
952 
953       //get code from key name
954       i = 0;
955       while (KeyName2ChCode[i].s != NULL)
956       {
957         string = KeyName2ChCode[i].s; len = strlen(string);
958         if (!strncmp(p, string, len)) {p += len; code = KeyName2ChCode[i].code | flags; break;}
959         i++;
960       }
961       if (code == 255) continue;
962 
963       while (*p && isspace(*p)) p++; //skip leading spaces
964 
965       //lookup move
966       i = 0;
967       while (MoveName2Move[i].s != NULL)
968       {
969         string = MoveName2Move[i].s; len = strlen(string);
970         if (!strncmp(p, string, len))
971         {
972           move = MoveName2Move[i].move;
973           move_used[move] = 1;
974 
975           if (move < M_THRUST) //non-cyber
976           {
977             if (num_bound < MAX_MOVE_KEYBINDS)
978             {
979               //add keybind to list
980               MoveKeybinds[num_bound].code = code;
981               MoveKeybinds[num_bound].move = move;
982               num_bound++;
983             }
984           }
985           else
986           {
987             if (num_cyber_bound < MAX_MOVE_KEYBINDS)
988             {
989               //add cyber keybind to list
990               MoveCyberKeybinds[num_cyber_bound].code = code;
991               MoveCyberKeybinds[num_cyber_bound].move = move;
992               num_cyber_bound++;
993             }
994           }
995 
996           break;
997         }
998         i++;
999       }
1000     }
1001 
1002     fclose(f);
1003   }
1004 
1005   //for moves that weren't referenced in file, bind default codes to them
1006   for (move = 0; move < NUM_MOVES; move++) if (!move_used[move])
1007   {
1008     i = 0;
1009     while (MoveKeybindsDefault[i].code != 255)
1010     {
1011       if (MoveKeybindsDefault[i].move == move)
1012       {
1013         code = MoveKeybindsDefault[i].code;
1014 
1015         if (move < M_THRUST) //non-cyber
1016         {
1017           if (num_bound < MAX_MOVE_KEYBINDS)
1018           {
1019             //add keybind to list
1020             MoveKeybinds[num_bound].code = code;
1021             MoveKeybinds[num_bound].move = move;
1022             num_bound++;
1023           }
1024         }
1025         else
1026         {
1027           if (num_cyber_bound < MAX_MOVE_KEYBINDS)
1028           {
1029             //add cyber keybind to list
1030             MoveCyberKeybinds[num_cyber_bound].code = code;
1031             MoveCyberKeybinds[num_cyber_bound].move = move;
1032             num_cyber_bound++;
1033           }
1034         }
1035       }
1036       i++;
1037     }
1038   }
1039 
1040   //signal end of lists
1041   MoveKeybinds[num_bound].code = 255;
1042   MoveCyberKeybinds[num_cyber_bound].code = 255;
1043 
1044   extern void init_motion_polling(void); //see movekeys.c
1045   init_motion_polling();
1046 }
1047 
1048 
1049 
1050 //************************************************************************************
1051 
1052 
1053 
1054 //*******************************
1055 //Create default keybinds file
1056 //*******************************
1057 
1058 
1059 
1060 #define JUSTIFY_COLUMN  30
1061 
1062 
1063 
1064 //if ch is 0, use code instead
WriteKeyName(int ch,int code,FILE * f)1065 static bool WriteKeyName(int ch, int code, FILE *f)
1066 {
1067   int i = 0, len = 0;
1068 
1069   while (KeyName2ChCode[i].s != NULL)
1070   {
1071     if (ch) { if (KeyName2ChCode[i].ch   == (ch   & 255)) break; }
1072     else    { if (KeyName2ChCode[i].code == (code & 255)) break; }
1073     i++;
1074   }
1075   if (KeyName2ChCode[i].s == NULL) return 0;
1076 
1077   fputs("bind  ", f); len += 6;
1078 
1079   if ((ch ? ch : code) & KB_FLAG_SHIFT) {fputs("shift+", f); len += 6;}
1080   if ((ch ? ch : code) &  KB_FLAG_CTRL) {fputs("ctrl+",  f); len += 5;}
1081   if ((ch ? ch : code) &   KB_FLAG_ALT) {fputs("alt+",   f); len += 4;}
1082 
1083   fputs(KeyName2ChCode[i].s, f); len += strlen(KeyName2ChCode[i].s);
1084 
1085   //add spaces to justify following text
1086   while (len < JUSTIFY_COLUMN) {fputc(' ', f); len++;}
1087 
1088   return 1;
1089 }
1090 
1091 
1092 
WriteMoveName(int move,FILE * f)1093 static void WriteMoveName(int move, FILE *f)
1094 {
1095   int i;
1096 
1097   //find move name that matches move
1098   i = 0;
1099   while (MoveName2Move[i].s != NULL)
1100   {
1101     if (MoveName2Move[i].move == move) break;
1102     i++;
1103   }
1104 
1105   if (MoveName2Move[i].s != NULL)
1106   {
1107     fputs(MoveName2Move[i].s, f);
1108     fputs("\n", f);
1109   }
1110 }
1111 
1112 
1113 
1114 //create default keybinds file if it doesn't already exist
CreateDefaultKeybindsFile(void)1115 void CreateDefaultKeybindsFile(void)
1116 {
1117   FILE *f;
1118   char *filename = GetKeybindsPathFilename();
1119   int i, ch;
1120 
1121   //check if file already exists; if so, return
1122   f = fopen(filename, "r");
1123   if (f != NULL) {fclose(f); return;}
1124 
1125   //open new file for writing
1126   f = fopen(filename, "w");
1127   if (f == NULL) return;
1128 
1129   //write default hotkey keybinds
1130   i = 0;
1131   while (HotKeyLookup[i].s)
1132   {
1133     //default 1 if it exists (it should)
1134     ch = HotKeyLookup[i].def1;
1135     if (ch && WriteKeyName(ch, 0, f))
1136     {
1137       fputs(HotKeyLookup[i].s, f);
1138       fputs("\n", f);
1139     }
1140 
1141     //default 2 if it exists (it might not)
1142     ch = HotKeyLookup[i].def2;
1143     if (ch && WriteKeyName(ch, 0, f))
1144     {
1145       fputs(HotKeyLookup[i].s, f);
1146       fputs("\n", f);
1147     }
1148 
1149     i++;
1150   }
1151 
1152   //write default fire keybind
1153   fputs("\n", f);
1154   WriteKeyName(KEY_ENTER, 0, f);
1155   fputs("\"fire\"\n\n", f);
1156 
1157   //write default move keybinds
1158   i = 0;
1159   while (MoveKeybindsDefault[i].code != 255)
1160   {
1161     if (WriteKeyName(0, MoveKeybindsDefault[i].code, f))
1162       WriteMoveName(MoveKeybindsDefault[i].move, f);
1163 
1164     i++;
1165   }
1166 
1167   fclose(f);
1168 }
1169