1 /*
2 * Copyright (C) 2001-2013 The Exult Team
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17 */
18
19 #ifdef HAVE_CONFIG_H
20 # include <config.h>
21 #endif
22
23 #include "SDL_keyboard.h"
24
25 #include "actors.h"
26 #include "keys.h"
27 #include "exult.h"
28 #include "game.h"
29 #include "cheat.h"
30 #include "U7file.h"
31 #include "ucmachine.h"
32 #include "Scroll_gump.h"
33 #include "mouse.h"
34 #include "gamewin.h"
35 #include "utils.h"
36 #include "keyactions.h"
37
38 using std::atoi;
39 using std::cerr;
40 using std::cout;
41 using std::endl;
42 using std::ifstream;
43 using std::isspace;
44 using std::strchr;
45 using std::string;
46 using std::strlen;
47
48 static class Chardata { // ctype-like character lists
49 public:
50 std::string whitespace;
Chardata()51 Chardata() {
52 for (size_t i = 0; i < 256; i++)
53 if (isspace(i))
54 whitespace += static_cast<char>(i);
55 }
56 } chardata;
57
58 using ActionFunc = void (*)(int const *);
59
60 const struct Action {
61 const char *s;
62 ActionFunc func;
63 ActionFunc func_release;
64 const char *desc;
65 enum {
66 dont_show = 0,
67 normal_keys,
68 cheat_keys,
69 mapedit_keys
70 } key_type;
71 Exult_Game game;
72 bool allow_during_dont_move;
73 bool allow_if_cant_act;
74 bool allow_if_cant_act_charmed;
75 bool is_movement;
76 } ExultActions[] = {
77 { "QUIT", ActionQuit, nullptr, "Quit", Action::normal_keys, NONE, true, true, true, false },
78 { "SAVE_RESTORE", ActionFileGump, nullptr, "Save/restore", Action::normal_keys, NONE, true, true, true, false },
79 { "QUICKSAVE", ActionQuicksave, nullptr, "Quick-save", Action::normal_keys, NONE, false, true, true, false },
80 {
81 "QUICKRESTORE",
82 ActionQuickrestore, nullptr, "Quick-restore", Action::normal_keys, NONE, true, true, true, false
83 },
84 { "ABOUT", ActionAbout, nullptr, "About Exult", Action::normal_keys, NONE, false, true, true, false },
85 { "HELP", ActionHelp, nullptr, "List keys", Action::normal_keys, NONE, false, true, true, false },
86 { "CLOSE_GUMPS", ActionCloseGumps, nullptr, "Close gumps", Action::dont_show, NONE, false, true, true, false },
87 { "CLOSE_OR_MENU", ActionCloseOrMenu, nullptr, "Game menu", Action::normal_keys, NONE, true, true, true, false },
88 {
89 "SCREENSHOT",
90 ActionScreenshot, nullptr, "Take screenshot", Action::normal_keys, NONE, true, true, true, false
91 },
92 { "GAME_MENU", ActionMenuGump, nullptr, "Game Menu", Action::normal_keys, NONE, true, true, true, false },
93 {
94 "OLD_FILE_GUMP",
95 ActionOldFileGump, nullptr, "Save/restore", Action::normal_keys, NONE, true, true, true, false
96 },
97 {
98 "SCALEVAL_INCREASE",
99 ActionScalevalIncrease, nullptr, "Increase scaleval", Action::mapedit_keys, NONE, true, true, true, false
100 },
101 {
102 "SCALEVAL_DECREASE",
103 ActionScalevalDecrease, nullptr, "Decrease scaleval", Action::mapedit_keys, NONE, true, true, true, false
104 },
105 {
106 "BRIGHTER",
107 ActionBrighter, nullptr, "Increase brightness", Action::normal_keys, NONE, true, true, true, false
108 },
109 { "DARKER", ActionDarker, nullptr, "Decrease brightness", Action::normal_keys, NONE, true, true, true, false },
110 {
111 "TOGGLE_FULLSCREEN",
112 ActionFullscreen, nullptr, "Toggle fullscreen", Action::normal_keys, NONE, true, true, true, false
113 },
114
115 { "USEITEM", ActionUseItem, nullptr, "Use item", Action::dont_show, NONE, false, false, false, false },
116 { "USEFOOD", ActionUseFood, nullptr, "Feed", Action::normal_keys, NONE, false, false, false, false },
117 { "CALL_USECODE", ActionCallUsecode, nullptr, "Call Usecode", Action::dont_show, NONE, false, false, false, false },
118 { "TOGGLE_COMBAT", ActionCombat, nullptr, "Toggle combat", Action::normal_keys, NONE, false, false, true, false },
119 { "PAUSE_COMBAT", ActionCombatPause, nullptr, "Pause combat", Action::normal_keys, NONE, false, false, true, false },
120 { "TARGET_MODE", ActionTarget, nullptr, "Target mode", Action::normal_keys, NONE, false, false, true, false },
121 { "INVENTORY", ActionInventory, nullptr, "Show inventory", Action::normal_keys, NONE, false, false, true, false },
122 { "TRY_KEYS", ActionTryKeys, nullptr, "Try keys", Action::normal_keys, NONE, false, false, false, false },
123 { "STATS", ActionStats, nullptr, "Show stats", Action::normal_keys, NONE, false, true, true, false },
124 {
125 "COMBAT_STATS",
126 ActionCombatStats, nullptr, "Show combat stats", Action::normal_keys, SERPENT_ISLE, false, false, true, false
127 },
128 {
129 "FACE_STATS",
130 ActionFaceStats, nullptr, "Change Face Stats State", Action::normal_keys, NONE, false, true, true, false
131 },
132 { "USE_HEALING_ITEMS", ActionUseHealingItems, nullptr, "Use bandages or healing potions", Action::normal_keys, NONE, false, false, false, false },
133 {
134 "SHOW_SI_INTRO",
135 ActionSIIntro, nullptr, "Show Alternate SI intro", Action::cheat_keys, SERPENT_ISLE, false, true, true, false
136 },
137 { "SHOW_ENDGAME", ActionEndgame, nullptr, "Show endgame", Action::cheat_keys, NONE, false, true, true, false },
138 { "SCROLL_LEFT", ActionScrollLeft, nullptr, "Scroll left", Action::cheat_keys, NONE, false, true , true, false},
139 { "SCROLL_RIGHT", ActionScrollRight, nullptr, "Scroll right", Action::cheat_keys, NONE, false, true, true, false },
140 { "SCROLL_UP", ActionScrollUp, nullptr, "Scroll up", Action::cheat_keys, NONE, false, true, true, false },
141 { "SCROLL_DOWN", ActionScrollDown, nullptr, "Scroll down", Action::cheat_keys, NONE, false, true, true, false },
142 { "WALK_WEST", ActionWalkWest, ActionStopWalking, "Walk west", Action::normal_keys, NONE, false, false, false, true },
143 { "WALK_EAST", ActionWalkEast, ActionStopWalking, "Walk east", Action::normal_keys, NONE, false, false, false, true },
144 { "WALK_NORTH", ActionWalkNorth, ActionStopWalking, "Walk north", Action::normal_keys, NONE, false, false, false, true },
145 { "WALK_SOUTH", ActionWalkSouth, ActionStopWalking, "Walk south", Action::normal_keys, NONE, false, false, false, true },
146 { "WALK_NORTH_EAST", ActionWalkNorthEast, ActionStopWalking, "Walk north-east", Action::normal_keys, NONE, false, false, false, true },
147 { "WALK_SOUTH_EAST", ActionWalkSouthEast, ActionStopWalking, "Walk south-east", Action::normal_keys, NONE, false, false, false, true },
148 { "WALK_NORTH_WEST", ActionWalkNorthWest, ActionStopWalking, "Walk north-west", Action::normal_keys, NONE, false, false, false, true },
149 { "WALK_SOUTH_WEST", ActionWalkSouthWest, ActionStopWalking, "Walk south-west", Action::normal_keys, NONE, false, false, false, true },
150 { "CENTER_SCREEN", ActionCenter, nullptr, "Center screen", Action::cheat_keys, NONE, false, true, true, false },
151 {
152 "SHAPE_BROWSER",
153 ActionShapeBrowser, nullptr, "Shape browser", Action::cheat_keys, NONE, false, true, true, false
154 },
155 { "SHAPE_BROWSER_HELP", ActionShapeBrowserHelp, nullptr, "List shape browser keys", Action::cheat_keys, NONE, false, true, true, false },
156 {
157 "CREATE_ITEM",
158 ActionCreateShape, nullptr, "Create last shape", Action::cheat_keys, NONE, false, true, true, false
159 },
160 {
161 "DELETE_OBJECT",
162 ActionDeleteObject, nullptr, "Delete object", Action::cheat_keys, NONE, false, true, true, false
163 },
164 {
165 "TOGGLE_EGGS",
166 ActionToggleEggs, nullptr, "Toggle egg display", Action::cheat_keys, NONE, false, true, true, false
167 },
168 {
169 "TOGGLE_GOD_MODE",
170 ActionGodMode, nullptr, "Toggle god mode", Action::cheat_keys, NONE, false, true, true, false
171 },
172 {
173 "CHANGE_GENDER",
174 ActionGender, nullptr, "Change gender", Action::cheat_keys, NONE, false, true, true, false
175 },
176 {
177 "CHEAT_HELP",
178 ActionCheatHelp, nullptr, "List cheat keys", Action::cheat_keys, NONE, false, true, true, false
179 },
180 {
181 "TOGGLE_INFRAVISION",
182 ActionInfravision, nullptr, "Toggle infravision", Action::cheat_keys, NONE, false, true, true, false
183 },
184 {
185 "TOGGLE_HACK_MOVER",
186 ActionHackMover, nullptr, "Toggle hack-mover mode", Action::cheat_keys, NONE, false, true, true, false
187 },
188 {
189 "MAP_TELEPORT",
190 ActionMapTeleport, nullptr, "Map teleport", Action::cheat_keys, NONE, false, true, true, false
191 },
192 {
193 "CURSOR_TELEPORT",
194 ActionTeleport, nullptr, "Teleport to cursor", Action::cheat_keys, NONE, false, true, true, false
195 },
196 {
197 "TARGET_MODE_TELEPORT",
198 ActionTeleportTargetMode, nullptr, "Bring up cursor to teleport", Action::cheat_keys, NONE, false, true, true, false
199 },
200 {
201 "NEXT_MAP_TELEPORT",
202 ActionNextMapTeleport, nullptr, "Teleport to next map", Action::cheat_keys, NONE, false, true, true, false
203 },
204 {
205 "NEXT_TIME_PERIOD",
206 ActionTime, nullptr, "Next time period", Action::cheat_keys, NONE, false, true, true, false
207 },
208 {
209 "TOGGLE_WIZARD_MODE",
210 ActionWizard, nullptr, "Toggle archwizard mode", Action::cheat_keys, NONE, false, true, true, false
211 },
212 { "PARTY_HEAL", ActionHeal, nullptr, "Heal party", Action::cheat_keys, NONE, false, true, true, false },
213 {
214 "PARTY_INCREASE_LEVEL",
215 ActionLevelup, nullptr, "Level-up party", Action::cheat_keys, NONE, false, true, true, false
216 },
217 {
218 "CHEAT_SCREEN",
219 ActionCheatScreen, nullptr, "Cheat Screen", Action::cheat_keys, NONE, true, true, true, false
220 },
221 {
222 "PICK_POCKET",
223 ActionPickPocket, nullptr, "Toggle Pick Pocket", Action::cheat_keys, NONE, false, true, true, false
224 },
225 {
226 "NPC_NUMBERS",
227 ActionNPCNumbers, nullptr, "Toggle NPC Numbers", Action::cheat_keys, NONE, false, true, true, false
228 },
229 {
230 "GRAB_ACTOR",
231 ActionGrabActor, nullptr, "Grab NPC for Cheat Screen", Action::cheat_keys, NONE, false, true, true, false
232 },
233 { "PLAY_MUSIC", ActionPlayMusic, nullptr, "Play song", Action::cheat_keys, NONE, false, true, true, false },
234 {
235 "TOGGLE_NAKED",
236 ActionNaked, nullptr, "Toggle naked mode", Action::cheat_keys, SERPENT_ISLE, false, true, true, false
237 },
238 {
239 "TOGGLE_PETRA",
240 ActionPetra, nullptr, "Toggle Petra mode", Action::cheat_keys, SERPENT_ISLE, false, true, true, false
241 },
242 {
243 "CHANGE_SKIN",
244 ActionSkinColour, nullptr, "Change skin colour", Action::cheat_keys, NONE, false, true, true, false
245 },
246 {
247 "NOTEBOOK", ActionNotebook, nullptr, "Show notebook", Action::normal_keys,
248 NONE, false, false, false, false
249 },
250 {
251 "SOUND_TESTER",
252 ActionSoundTester, nullptr, "Sound tester", Action::cheat_keys, NONE, false, true, true, false
253 },
254 { "TEST", ActionTest, nullptr, "Test", Action::dont_show, NONE, false, true, true, false },
255
256 {
257 "MAPEDIT_HELP",
258 ActionMapeditHelp, nullptr, "List mapedit keys", Action::mapedit_keys, NONE, false, true, true, false
259 },
260 {
261 "TOGGLE_MAP_EDITOR",
262 ActionMapEditor, nullptr, "Toggle map-editor mode", Action::mapedit_keys, NONE, true, true, true, false
263 },
264 {
265 "SKIPLIFT_DECREMENT",
266 ActionSkipLift, nullptr, "Decrement skiplift", Action::mapedit_keys, NONE, false, true, true, false
267 },
268 {
269 "CUT",
270 ActionCut, nullptr, "Cut Selected Objects", Action::mapedit_keys, NONE, true, true, true, false
271 },
272 {
273 "COPY",
274 ActionCopy, nullptr, "Copy Selected Objects", Action::mapedit_keys, NONE, true, true, true, false
275 },
276 {
277 "PASTE",
278 ActionPaste, nullptr, "Paste Selected Objects", Action::mapedit_keys, NONE, true, true, true, false
279 },
280 {
281 "DELETE_SELECTED",
282 ActionDeleteSelected, nullptr, "Delete selected", Action::mapedit_keys, NONE, true, true, true, false
283 },
284 {
285 "MOVE_SELECTED",
286 ActionMoveSelected, nullptr, "Move selected", Action::mapedit_keys, NONE, true, true, true, false
287 },
288 {
289 "WRITE_MINIMAP",
290 ActionWriteMiniMap, nullptr, "Write minimap", Action::mapedit_keys, NONE, false, true, true, false
291 },
292 { "REPAINT", ActionRepaint, nullptr, "Repaint screen", Action::dont_show, NONE, true, true, true, false },
293 { "", nullptr, nullptr, "", Action::dont_show, NONE, false, false, false, false } //terminator
294 };
295
296 const struct {
297 const char *s;
298 SDL_Keycode k;
299 } SDLKeyStringTable[] = {
300 {"BACKSPACE", SDLK_BACKSPACE},
301 {"TAB", SDLK_TAB},
302 {"ENTER", SDLK_RETURN},
303 {"PAUSE", SDLK_PAUSE},
304 {"ESC", SDLK_ESCAPE},
305 {"SPACE", SDLK_SPACE},
306 {"DEL", SDLK_DELETE},
307 {"KP0", SDLK_KP_0},
308 {"KP1", SDLK_KP_1},
309 {"KP2", SDLK_KP_2},
310 {"KP3", SDLK_KP_3},
311 {"KP4", SDLK_KP_4},
312 {"KP5", SDLK_KP_5},
313 {"KP6", SDLK_KP_6},
314 {"KP7", SDLK_KP_7},
315 {"KP8", SDLK_KP_8},
316 {"KP9", SDLK_KP_9},
317 {"KP0", SDLK_KP_0},
318 {"KP.", SDLK_KP_PERIOD},
319 {"KP/", SDLK_KP_DIVIDE},
320 {"KP*", SDLK_KP_MULTIPLY},
321 {"KP-", SDLK_KP_MINUS},
322 {"KP+", SDLK_KP_PLUS},
323 {"KP_ENTER", SDLK_KP_ENTER},
324 {"UP", SDLK_UP},
325 {"DOWN", SDLK_DOWN},
326 {"RIGHT", SDLK_RIGHT},
327 {"LEFT", SDLK_LEFT},
328 {"INSERT", SDLK_INSERT},
329 {"HOME", SDLK_HOME},
330 {"END", SDLK_END},
331 {"PAGEUP", SDLK_PAGEUP},
332 {"PAGEDOWN", SDLK_PAGEDOWN},
333 {"F1", SDLK_F1},
334 {"F2", SDLK_F2},
335 {"F3", SDLK_F3},
336 {"F4", SDLK_F4},
337 {"F5", SDLK_F5},
338 {"F6", SDLK_F6},
339 {"F7", SDLK_F7},
340 {"F8", SDLK_F8},
341 {"F9", SDLK_F9},
342 {"F10", SDLK_F10},
343 {"F11", SDLK_F11},
344 {"F12", SDLK_F12},
345 {"F13", SDLK_F13},
346 {"F14", SDLK_F14},
347 {"F15", SDLK_F15},
348 {"", SDLK_UNKNOWN} // terminator
349 };
350
351
352 using ParseKeyMap = std::map<std::string, SDL_Keycode>;
353 using ParseActionMap = std::map<std::string, const Action *>;
354
355 static ParseKeyMap keys;
356 static ParseActionMap actions;
357
358
KeyBinder()359 KeyBinder::KeyBinder() {
360 FillParseMaps();
361 }
362
AddKeyBinding(SDL_Keycode key,int mod,const Action * action,int nparams,const int * params)363 void KeyBinder::AddKeyBinding(SDL_Keycode key, int mod, const Action *action,
364 int nparams, const int *params) {
365 SDL_Keysym k;
366 ActionType a;
367 k.scancode = static_cast<SDL_Scancode>(0);
368 k.sym = key;
369 k.mod = static_cast<SDL_Keymod>(mod);
370 a.action = action;
371 int i; // For MSVC
372 for (i = 0; i < c_maxparams && i < nparams; i++)
373 a.params[i] = params[i];
374 for (i = nparams; i < c_maxparams; i++)
375 a.params[i] = -1;
376
377 bindings[k] = a;
378 }
379
DoAction(ActionType const & a,bool press) const380 bool KeyBinder::DoAction(ActionType const &a, bool press) const {
381 if (!cheat() && (a.action->key_type == Action::cheat_keys
382 || a.action->key_type == Action::mapedit_keys))
383 return true;
384 if (a.action->game != NONE && a.action->game != Game::get_game_type())
385 return true;
386
387 // Restrict key actions in dont_move mode
388 if (!a.action->allow_during_dont_move
389 && Game_window::get_instance()->main_actor_dont_move())
390 return true;
391
392 // Restrict keys if avatar is sleeping/paralyzed/unconscious/dead
393 if (!a.action->allow_if_cant_act
394 && !Game_window::get_instance()->main_actor_can_act())
395 return true;
396 if (!a.action->allow_if_cant_act_charmed
397 && !Game_window::get_instance()->main_actor_can_act_charmed())
398 return true;
399 if (press) {
400 #ifndef USE_EXULTSTUDIO
401 // Display unsupported message if no ES support.
402 if (a.action->key_type == Action::mapedit_keys) {
403 Scroll_gump *scroll;
404 scroll = new Scroll_gump();
405
406 scroll->add_text("Map editor functionality is disabled in this version of Exult.\n");
407 scroll->add_text("If you want or need this functionality, you should install a snapshot, as they have support for Exult Studio.\n");
408 scroll->add_text("If no snapshots are available for your platform, you will have to compile it on your own.\n");
409
410 scroll->paint();
411 do {
412 int x, y;
413 Get_click(x, y, Mouse::hand, nullptr, false, scroll);
414 } while (scroll->show_next_page());
415 Game_window::get_instance()->paint();
416 delete scroll;
417 return true;
418 }
419 #endif
420 a.action->func(a.params);
421 } else {
422 #ifndef USE_EXULTSTUDIO
423 // Do nothing when releasing mapedit keys if no ES support.
424 if (a.action->key_type == Action::mapedit_keys)
425 return true;
426 #endif
427 if (a.action->func_release != nullptr)
428 a.action->func_release(a.params);
429 }
430
431 return true;
432 }
433
TranslateEvent(SDL_Event const & ev) const434 KeyMap::const_iterator KeyBinder::TranslateEvent(SDL_Event const &ev) const {
435 SDL_Keysym key = ev.key.keysym;
436
437 if (ev.type != SDL_KEYDOWN && ev.type != SDL_KEYUP)
438 return bindings.end();
439
440 key.mod = KMOD_NONE;
441 if (ev.key.keysym.mod & KMOD_SHIFT)
442 key.mod = static_cast<SDL_Keymod>(key.mod | KMOD_SHIFT);
443 if (ev.key.keysym.mod & KMOD_CTRL)
444 key.mod = static_cast<SDL_Keymod>(key.mod | KMOD_CTRL);
445 #ifdef MACOSX
446 // map Meta to Alt on OS X
447 if (ev.key.keysym.mod & KMOD_GUI)
448 key.mod = static_cast<SDL_Keymod>(key.mod | KMOD_ALT);
449 #else
450 if (ev.key.keysym.mod & KMOD_ALT)
451 key.mod = static_cast<SDL_Keymod>(key.mod | KMOD_ALT);
452 #endif
453
454 return bindings.find(key);
455 }
456
HandleEvent(SDL_Event const & ev) const457 bool KeyBinder::HandleEvent(SDL_Event const &ev) const {
458 auto sdlkey_index = TranslateEvent(ev);
459 if (sdlkey_index != bindings.end())
460 return DoAction(sdlkey_index->second, ev.type == SDL_KEYDOWN);
461
462 return false;
463 }
464
IsMotionEvent(SDL_Event const & ev) const465 bool KeyBinder::IsMotionEvent(SDL_Event const &ev) const {
466 auto sdlkey_index = TranslateEvent(ev);
467 if (sdlkey_index == bindings.end())
468 return false;
469 ActionType const &act = sdlkey_index->second;
470 return act.action->is_movement;
471 }
472
ShowHelp() const473 void KeyBinder::ShowHelp() const {
474 Scroll_gump *scroll;
475 scroll = new Scroll_gump();
476
477 for (const auto& iter : keyhelp)
478 scroll->add_text(iter.c_str());
479
480 scroll->paint();
481 do {
482 int x;
483 int y;
484 Get_click(x, y, Mouse::hand, nullptr, false, scroll);
485 } while (scroll->show_next_page());
486 Game_window::get_instance()->paint();
487 delete scroll;
488 }
489
ShowCheatHelp() const490 void KeyBinder::ShowCheatHelp() const {
491 Scroll_gump *scroll;
492 scroll = new Scroll_gump();
493
494 for (const auto& iter : cheathelp)
495 scroll->add_text(iter.c_str());
496
497 scroll->paint();
498 do {
499 int x;
500 int y;
501 Get_click(x, y, Mouse::hand, nullptr, false, scroll);
502 } while (scroll->show_next_page());
503 Game_window::get_instance()->paint();
504 delete scroll;
505 }
506
ShowMapeditHelp() const507 void KeyBinder::ShowMapeditHelp() const {
508 Scroll_gump *scroll;
509 scroll = new Scroll_gump();
510
511 for (const auto& iter : mapedithelp)
512 scroll->add_text(iter.c_str());
513
514 scroll->paint();
515 do {
516 int x;
517 int y;
518 Get_click(x, y, Mouse::hand, nullptr, false, scroll);
519 } while (scroll->show_next_page());
520 Game_window::get_instance()->paint();
521 delete scroll;
522 }
523
ShowBrowserKeys() const524 void KeyBinder::ShowBrowserKeys() const {
525 auto *scroll = new Scroll_gump();
526 scroll->add_text("Esc - Exits the shape browser");
527 scroll->add_text("down - Increase shape by 1");
528 scroll->add_text("S - Increase shape by 1");
529 scroll->add_text("up - Decrease shape by 1");
530 scroll->add_text("Shift-S - Decrease shape by 1");
531 scroll->add_text("Page down - Increase shape by 20");
532 scroll->add_text("J - Increase shape by 20");
533 scroll->add_text("Page up - Decrease shape by 20");
534 scroll->add_text("Shift-J - Decrease shape by 20");
535 scroll->add_text("right - Increase frame by 1");
536 scroll->add_text("F - Increase frame by 1");
537 scroll->add_text("left - Decrease frame by 1");
538 scroll->add_text("Shift-F - Decrease frame by 1");
539 scroll->add_text("V - Increase vga file by 1");
540 scroll->add_text("Shift-V - Decrease vga file by 1");
541 scroll->add_text("P - Increase palette by 1");
542 scroll->add_text("Shift-P - Decrease palette by 1");
543 scroll->add_text("X - Increase xform by 1");
544 scroll->add_text("Shift-X - Decrease xform by 1");
545 char returned_key[200];
546 if (last_created_key.empty())
547 strcpy(returned_key, "Error: No key assigned");
548 else {
549 strcpy(returned_key, ""); // prevent garbage text
550 int extra_keys = 0;
551 for (const auto& iter : last_created_key) {
552 if (extra_keys >= 5)
553 continue;
554 else if (extra_keys > 0)
555 strcat(returned_key, " or ");
556 strcat(returned_key, iter.c_str());
557 extra_keys += 1;
558 }
559 }
560 strcat(returned_key, " - when pressed in game will create the last shape viewed in shapes.vga.");
561 scroll->add_text(returned_key);
562 scroll->paint();
563 do {
564 int x;
565 int y;
566 Get_click(x, y, Mouse::hand, nullptr, false, scroll);
567 } while (scroll->show_next_page());
568 Game_window::get_instance()->paint();
569 delete scroll;
570 }
571
ParseText(char * text,int len)572 void KeyBinder::ParseText(char *text, int len) {
573 char *ptr;
574 char *end;
575 const char LF = '\n';
576
577 ptr = text;
578
579 // last (useful) line must end with LF
580 while ((ptr - text) < len && (end = strchr(ptr, LF)) != nullptr) {
581 *end = '\0';
582 ParseLine(ptr);
583 ptr = end + 1;
584 }
585 }
586
skipspace(string & s)587 static void skipspace(string &s) {
588 size_t i = s.find_first_not_of(chardata.whitespace);
589 if (i && i != string::npos)
590 s.erase(0, i);
591 }
592
593
ParseLine(char * line)594 void KeyBinder::ParseLine(char *line) {
595 size_t i;
596 SDL_Keysym k;
597 ActionType a;
598 k.sym = SDLK_UNKNOWN;
599 k.mod = KMOD_NONE;
600 string s = line;
601 string u;
602 string d;
603 string desc;
604 string keycode;
605 bool show;
606
607 skipspace(s);
608
609 // comments and empty lines
610 if (s.length() == 0 || s[0] == '#')
611 return;
612
613 u = s;
614 to_uppercase(u);
615
616 // get key
617 while (s.length() && !isspace(static_cast<unsigned char>(s[0]))) {
618 // check modifiers
619 // if (u.compare("ALT-",0,4) == 0) {
620 if (u.substr(0, 4) == "ALT-") {
621 k.mod = static_cast<SDL_Keymod>(k.mod | KMOD_ALT);
622 s.erase(0, 4);
623 u.erase(0, 4);
624 // } else if (u.compare("CTRL-",0,5) == 0) {
625 } else if (u.substr(0, 5) == "CTRL-") {
626 k.mod = static_cast<SDL_Keymod>(k.mod | KMOD_CTRL);
627 s.erase(0, 5);
628 u.erase(0, 5);
629 // } else if (u.compare("SHIFT-",0,6) == 0) {
630 } else if (u.substr(0, 6) == "SHIFT-") {
631 k.mod = static_cast<SDL_Keymod>(k.mod | KMOD_SHIFT);
632 s.erase(0, 6);
633 u.erase(0, 6);
634 } else {
635 i = s.find_first_of(chardata.whitespace);
636
637 keycode = s.substr(0, i);
638 s.erase(0, i);
639 string t(keycode);
640 to_uppercase(t);
641
642 if (t.length() == 0) {
643 cerr << "Keybinder: parse error in line: " << s << endl;
644 return;
645 } else if (t.length() == 1) {
646 // translate 1-letter keys straight to SDL_Keycode
647 auto c = static_cast<unsigned char>(t[0]);
648 if (std::isgraph(c) && c != '%' && c != '{' && c != '|' && c != '}' && c != '~') {
649 c = std::tolower(c); // need lowercase
650 k.sym = static_cast<SDL_Keycode>(c);
651 } else {
652 cerr << "Keybinder: unsupported key: " << keycode << endl;
653 }
654 } else {
655 // lookup in table
656 auto key_index = keys.find(t);
657 if (key_index != keys.end()) {
658 k.sym = (*key_index).second;
659 } else {
660 cerr << "Keybinder: unsupported key: " << keycode << endl;
661 return;
662 }
663 }
664 }
665 }
666
667 if (k.sym == SDLK_UNKNOWN) {
668 cerr << "Keybinder: parse error in line: " << s << endl;
669 return;
670 }
671
672 // get function
673 skipspace(s);
674
675 i = s.find_first_of(chardata.whitespace);
676 string t = s.substr(0, i);
677 s.erase(0, i);
678 to_uppercase(t);
679
680 auto action_index = actions.find(t);
681 if (action_index != actions.end()) {
682 a.action = (*action_index).second;
683 } else {
684 cerr << "Keybinder: unsupported action: " << t << endl;
685 return;
686 }
687
688 // get params
689 skipspace(s);
690
691 int np = 0;
692 // Special case.
693 if (!strcmp(a.action->s, "CALL_USECODE")) {
694 // Want to allow function name.
695 if (s.length() && s[0] != '#') {
696 i = s.find_first_of(chardata.whitespace);
697 string t = s.substr(0, i);
698 s.erase(0, i);
699 skipspace(s);
700
701 int p = atoi(t.c_str());
702 if (!p) {
703 // No conversion? Try as function name.
704 Usecode_machine *usecode =
705 Game_window::get_instance()->get_usecode();
706 p = usecode->find_function(t.c_str());
707 } else if (p < 0) {
708 p = -1;
709 }
710 a.params[np++] = p;
711 }
712 }
713 while (s.length() && s[0] != '#' && np < c_maxparams) {
714 i = s.find_first_of(chardata.whitespace);
715 string t = s.substr(0, i);
716 s.erase(0, i);
717 skipspace(s);
718
719 int p = atoi(t.c_str());
720 a.params[np++] = p;
721 }
722
723 // read optional help comment
724 if (s.length() >= 1 && s[0] == '#') {
725 if (s.length() >= 2 && s[1] == '-') {
726 show = false;
727 } else {
728 s.erase(0, 1);
729 skipspace(s);
730 d = s;
731 // Always show if there is a comment.
732 show = true;
733 }
734 } else {
735 // Action::dont_show doesn't have default display names, so do not
736 // show them if they don't have a comment.
737 d = a.action->desc;
738 show = a.action->key_type != Action::dont_show;
739 }
740
741 if (show) {
742 desc = "";
743 if (k.mod & KMOD_CTRL)
744 desc += "Ctrl-";
745 #ifdef MACOSX
746 if (k.mod & KMOD_ALT)
747 desc += "Cmd-";
748 #else
749 if (k.mod & KMOD_ALT)
750 desc += "Alt-";
751 #endif
752 if (k.mod & KMOD_SHIFT)
753 desc += "Shift-";
754 desc += keycode;
755 if (!strcmp(a.action->s, "CREATE_ITEM") && a.params[0] == -1)
756 last_created_key.push_back(desc);
757
758 desc += " - " + d;
759
760 // add to help list
761 if (a.action->key_type == Action::cheat_keys)
762 cheathelp.push_back(desc);
763 else if (a.action->key_type == Action::mapedit_keys)
764 mapedithelp.push_back(desc);
765 else // Either Action::normal_keys or Action::dont_show with comment.
766 keyhelp.push_back(desc);
767 }
768
769 // bind key
770 AddKeyBinding(k.sym, k.mod, a.action, np, a.params);
771 }
772
LoadFromFileInternal(const char * filename)773 void KeyBinder::LoadFromFileInternal(const char *filename) {
774 ifstream keyfile;
775
776 U7open(keyfile, filename, true);
777 char temp[1024]; // 1024 should be long enough
778 while (!keyfile.eof()) {
779 keyfile.getline(temp, 1024);
780 if (keyfile.gcount() >= 1023) {
781 cerr << "Keybinder: parse error: line too long. Skipping rest of file."
782 << endl;
783 return;
784 }
785 ParseLine(temp);
786 }
787 keyfile.close();
788 }
789
LoadFromFile(const char * filename)790 void KeyBinder::LoadFromFile(const char *filename) {
791
792 Flush();
793
794 cout << "Loading keybindings from file " << filename << endl;
795 LoadFromFileInternal(filename);
796 }
797
LoadFromPatch()798 void KeyBinder::LoadFromPatch() {
799 if (U7exists(PATCH_KEYS)) {
800 cout << "Loading patch keybindings" << endl;
801 LoadFromFileInternal(PATCH_KEYS);
802 }
803 }
804
LoadDefaults()805 void KeyBinder::LoadDefaults() {
806 Flush();
807
808 cout << "Loading default keybindings" << endl;
809
810 const str_int_pair &resource = game->get_resource("config/defaultkeys");
811
812 U7object txtobj(resource.str, resource.num);
813 size_t len;
814 auto txt = txtobj.retrieve(len);
815 if (txt && len > 0)
816 ParseText(reinterpret_cast<char*>(txt.get()), len);
817 }
818
819 // codes used in keybindings-files. (use uppercase here)
FillParseMaps()820 void KeyBinder::FillParseMaps() {
821 int i; // For MSVC
822 for (i = 0; strlen(SDLKeyStringTable[i].s) > 0; i++)
823 keys[SDLKeyStringTable[i].s] = SDLKeyStringTable[i].k;
824
825 for (i = 0; strlen(ExultActions[i].s) > 0; i++)
826 actions[ExultActions[i].s] = &(ExultActions[i]);
827 }
828