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