1 /*
2  * Portions of this file are copyright Rebirth contributors and licensed as
3  * described in COPYING.txt.
4  * Portions of this file are copyright Parallax Software and licensed
5  * according to the Parallax license below.
6  * See COPYING.txt for license details.
7 
8 THE COMPUTER CODE CONTAINED HEREIN IS THE SOLE PROPERTY OF PARALLAX
9 SOFTWARE CORPORATION ("PARALLAX").  PARALLAX, IN DISTRIBUTING THE CODE TO
10 END-USERS, AND SUBJECT TO ALL OF THE TERMS AND CONDITIONS HEREIN, GRANTS A
11 ROYALTY-FREE, PERPETUAL LICENSE TO SUCH END-USERS FOR USE BY SUCH END-USERS
12 IN USING, DISPLAYING,  AND CREATING DERIVATIVE WORKS THEREOF, SO LONG AS
13 SUCH USE, DISPLAY OR CREATION IS FOR NON-COMMERCIAL, ROYALTY OR REVENUE
14 FREE PURPOSES.  IN NO EVENT SHALL THE END-USER USE THE COMPUTER CODE
15 CONTAINED HEREIN FOR REVENUE-BEARING PURPOSES.  THE END-USER UNDERSTANDS
16 AND AGREES TO THE TERMS HEREIN AND ACCEPTS THE SAME BY USE OF THIS FILE.
17 COPYRIGHT 1993-1999 PARALLAX SOFTWARE CORPORATION.  ALL RIGHTS RESERVED.
18 */
19 
20 /*
21  *
22  * Routines to configure keyboard, joystick, etc..
23  *
24  */
25 
26 #include <algorithm>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <stdarg.h>
31 #include <ctype.h>
32 #include <cstddef>
33 #include <stdexcept>
34 #include <functional>
35 
36 #include "dxxerror.h"
37 #include "pstypes.h"
38 #include "gr.h"
39 #include "window.h"
40 #include "console.h"
41 #include "palette.h"
42 #include "physfsx.h"
43 #include "game.h"
44 #include "gamefont.h"
45 #include "u_mem.h"
46 #include "kconfig.h"
47 #include "digi.h"
48 #include "key.h"
49 #include "mouse.h"
50 #include "newmenu.h"
51 #include "multi.h"
52 #include "timer.h"
53 #include "text.h"
54 #include "player.h"
55 #include "args.h"
56 #include "playsave.h"
57 #include "screens.h"
58 
59 #include "d_array.h"
60 #include "d_range.h"
61 #include "d_zip.h"
62 #include "partial_range.h"
63 
64 using std::min;
65 using std::max;
66 using std::plus;
67 using std::minus;
68 
69 namespace dcx {
70 
71 // Array used to 'blink' the cursor while waiting for a keypress.
72 const std::array<int8_t, 64> fades{{
73 	1,1,1,2,2,3,4,4,5,6,8,9,10,12,13,15,
74 	16,17,19,20,22,23,24,26,27,28,28,29,30,30,31,31,
75 	31,31,31,30,30,29,28,28,27,26,24,23,22,20,19,17,
76 	16,15,13,12,10,9,8,6,5,4,4,3,2,2,1,1
77 }};
78 
79 const std::array<char[2], 2> invert_text{{"N", "Y"}};
80 #if DXX_MAX_JOYSTICKS
81 joybutton_text_t joybutton_text;
82 #endif
83 #if DXX_MAX_AXES_PER_JOYSTICK
84 joyaxis_text_t joyaxis_text;
85 #endif
86 constexpr char mouseaxis_text[][8] = { "L/R", "F/B", "WHEEL" };
87 constexpr char mousebutton_text[][8] = { "LEFT", "RIGHT", "MID", "M4", "M5", "M6", "M7", "M8", "M9", "M10","M11","M12","M13","M14","M15","M16" };
88 
89 const std::array<uint8_t, 19> system_keys{{
90 	KEY_ESC, KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5, KEY_F6, KEY_F7, KEY_F8, KEY_F9, KEY_F10, KEY_F11, KEY_F12, KEY_MINUS, KEY_EQUAL, KEY_PRINT_SCREEN,
91 	// KEY_*LOCK should always be last since we wanna skip these if -nostickykeys
92 	KEY_CAPSLOCK, KEY_SCROLLOCK, KEY_NUMLOCK
93 }};
94 
95 fix Cruise_speed=0;
96 
97 #define INFO_Y (188)
98 
99 const std::array<uint8_t, MAX_DXX_REBIRTH_CONTROLS> DefaultKeySettingsRebirth{{ 0x2,0xff,0xff,0x3,0xff,0xff,0x4,0xff,0xff,0x5,0xff,0xff,0x6,0xff,0xff,0x7,0xff,0xff,0x8,0xff,0xff,0x9,0xff,0xff,0xa,0xff,0xff,0xb,0xff,0xff }};
100 
kconfig_begin_loop(control_info & Controls)101 void kconfig_begin_loop(control_info &Controls)
102 {
103 	Controls.pitch_time = Controls.vertical_thrust_time = Controls.heading_time = Controls.sideways_thrust_time = Controls.bank_time = Controls.forward_thrust_time = 0;
104 }
105 
106 namespace {
107 
108 struct kc_mitem {
109 	uint8_t oldvalue;
110 	uint8_t value;		// what key,button,etc
111 };
112 
113 enum kc_type : uint8_t
114 {
115 	BT_KEY = 0,
116 	BT_MOUSE_BUTTON = 1,
117 	BT_MOUSE_AXIS = 2,
118 	BT_JOY_BUTTON = 3,
119 	BT_JOY_AXIS = 4,
120 	BT_INVERT = 5,
121 };
122 
123 enum kc_state : uint8_t
124 {
125 	STATE_NONE = 0,
126 	STATE_BIT1 = 1,
127 	STATE_BIT2 = 2,
128 	STATE_BIT3 = 4,
129 	STATE_BIT4 = 8,
130 	STATE_BIT5 = 16,
131 };
132 
kc_drawquestion(grs_canvas & canvas,const grs_font & cv_font,uint8_t & menu_fade_index,const short item_xinput,const short item_y,const int8_t item_w2,const color_palette_index color)133 static void kc_drawquestion(grs_canvas &canvas, const grs_font &cv_font, uint8_t &menu_fade_index, const short item_xinput, const short item_y, const int8_t item_w2, const color_palette_index color)
134 {
135 	if (++ menu_fade_index > 63)
136 		menu_fade_index = 0;
137 	const auto &&[w, h] = gr_get_string_size(cv_font, "?");
138 	const auto &&fspacx = FSPACX();
139 	const auto &&fspacy = FSPACY();
140 	const auto &&fspacx_item_xinput = fspacx(item_xinput);
141 	const auto &&fspacy_item_y = fspacy(item_y);
142 	gr_urect(canvas, fspacx_item_xinput, fspacy(item_y - 1), fspacx(item_xinput + item_w2), fspacy_item_y + h, color);
143 	gr_set_fontcolor(canvas, BM_XRGB(28, 28, 28), -1);
144 	const auto x = fspacx_item_xinput + ((fspacx(item_w2) - w) / 2);
145 	gr_string(canvas, cv_font, x, fspacy_item_y, "?", w, h);
146 }
147 
148 }
149 
150 }
151 
152 namespace dsx {
153 
154 control_info Controls;
155 
156 namespace {
157 
158 #define kc_item kc_item
159 struct kc_item
160 {
161 	const short x, y;              // x, y pos of label
162 	const short xinput;                // x pos of input field
163 	const int8_t w2;                // length of input field
164 	const uint8_t u,d,l,r;           // neighboring field ids for cursor navigation
165 	const kc_type type;
166 	const kc_state state_bit;
167 	union {
168 		uint8_t control_info::state_controls_t::*const ci_state_ptr;
169 		uint8_t control_info::state_controls_t::*const ci_count_ptr;
170 	};
171 };
172 
173 struct kc_menu : window
174 {
175 	using window::window;
176 	const char *litems;
177 	const kc_item	*items;
178 	kc_mitem	*mitems;
179 	const char	*title;
180 	unsigned	nitems;
181 	unsigned	citem = 0;
182 	uint8_t changing = 0;
183 	ubyte	q_fade_i;	// for flashing the question mark
184 	uint8_t mouse_state = 0;
185 	std::array<int, 3>	old_maxis;
186 #if DXX_MAX_AXES_PER_JOYSTICK
187 	std::array<int, JOY_MAX_AXES>	old_jaxis;
188 #endif
189 	virtual window_event_result event_handler(const d_event &) override;
190 };
191 
192 }
193 
194 const struct player_config::KeySettings DefaultKeySettings{
195 	/* Keyboard */ {{{
196 #if defined(DXX_BUILD_DESCENT_I)
197 		KEY_UP, KEY_PAD8, KEY_DOWN, KEY_PAD2, KEY_LEFT, KEY_PAD4, KEY_RIGHT, KEY_PAD6, KEY_LALT, 0xff, KEY_A, KEY_PAD1, KEY_D, KEY_PAD3, KEY_C, KEY_PADMINUS, KEY_X, KEY_PADPLUS, 0xff, 0xff, KEY_Q, KEY_PAD7, KEY_E, KEY_PAD9, KEY_LCTRL, KEY_RCTRL, KEY_SPACEBAR, 0xff, KEY_F, 0xff, KEY_W, 0xff, KEY_S, 0xff, KEY_B, 0xff, KEY_R, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, KEY_TAB, 0xff, KEY_COMMA, 0x0, KEY_PERIOD, 0x0
198 #elif defined(DXX_BUILD_DESCENT_II)
199 		KEY_UP, KEY_PAD8, KEY_DOWN, KEY_PAD2, KEY_LEFT, KEY_PAD4, KEY_RIGHT, KEY_PAD6, KEY_LALT, 0xff, KEY_A, KEY_PAD1, KEY_D, KEY_PAD3, KEY_C, KEY_PADMINUS, KEY_X, KEY_PADPLUS, 0xff, 0xff, KEY_Q, KEY_PAD7, KEY_E, KEY_PAD9, KEY_LCTRL, KEY_RCTRL, KEY_SPACEBAR, 0xff, KEY_F, 0xff, KEY_W, 0xff, KEY_S, 0xff, KEY_B, 0xff, KEY_R, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, KEY_TAB, 0xff, KEY_LSHIFT, 0xff, KEY_COMMA, 0xff, KEY_PERIOD, 0xff, KEY_H, 0xff, KEY_T, 0xff, 0xff, 0xff, 0x0, 0x0
200 #endif
201 	}}},
202 #if DXX_MAX_JOYSTICKS
203 	{{{
204 #if defined(DXX_BUILD_DESCENT_I)
205 		0x0, 0x1, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1, 0x0, 0x0, 0x0, 0xff, 0x0, 0xff, 0x0, 0xff, 0x0, 0xff, 0x0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
206 #elif defined(DXX_BUILD_DESCENT_II)
207 		0x0, 0x1, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1, 0x0, 0x0, 0x0, 0xff, 0x0, 0xff, 0x0, 0xff, 0x0, 0xff, 0x0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
208 #endif
209 	}}},
210 #endif
211 	/* Mouse */ {{{
212 #if defined(DXX_BUILD_DESCENT_I)
213 	0x0, 0x1, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1, 0x0, 0x0, 0x0, 0xff, 0x0, 0xff, 0x0, 0xff, 0x0, 0xff, 0x0, 0xff, 0xff, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0
214 #elif defined(DXX_BUILD_DESCENT_II)
215 	0x0, 0x1, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1, 0x0, 0x0, 0x0, 0xff, 0x0, 0xff, 0x0, 0xff, 0x0, 0xff, 0x0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0
216 #endif
217 	}}}
218 };
219 
220 #if defined(DXX_BUILD_DESCENT_I)
221 #include /* generated by similar/main/generate-kconfig-udlr.py */ "d1x-rebirth/kconfig.udlr.h"
222 #elif defined(DXX_BUILD_DESCENT_II)
223 #include /* generated by similar/main/generate-kconfig-udlr.py */ "d2x-rebirth/kconfig.udlr.h"
224 #endif
225 
226 namespace {
227 
228 #if defined(DXX_BUILD_DESCENT_I)
229 #define D2X_EXTENDED_WEAPON_STRING(X)
230 #elif defined(DXX_BUILD_DESCENT_II)
231 #define D2X_EXTENDED_WEAPON_STRING(X)	X
232 #endif
233 
234 #define WEAPON_STRING_LASER	D2X_EXTENDED_WEAPON_STRING("(SUPER)") "LASER CANNON"
235 #define WEAPON_STRING_VULCAN	"VULCAN" D2X_EXTENDED_WEAPON_STRING("/GAUSS") " CANNON"
236 #define WEAPON_STRING_SPREADFIRE	"SPREADFIRE" D2X_EXTENDED_WEAPON_STRING("/HELIX") " CANNON"
237 #define WEAPON_STRING_PLASMA	"PLASMA" D2X_EXTENDED_WEAPON_STRING("/PHOENIX") " CANNON"
238 #define WEAPON_STRING_FUSION	"FUSION" D2X_EXTENDED_WEAPON_STRING("/OMEGA") " CANNON"
239 #define WEAPON_STRING_CONCUSSION	"CONCUSSION" D2X_EXTENDED_WEAPON_STRING("/FLASH") " MISSILE"
240 #define WEAPON_STRING_HOMING	"HOMING" D2X_EXTENDED_WEAPON_STRING("/GUIDED") " MISSILE"
241 #define WEAPON_STRING_PROXIMITY	"PROXIMITY BOMB" D2X_EXTENDED_WEAPON_STRING("/SMART MINE")
242 #define WEAPON_STRING_SMART	"SMART" D2X_EXTENDED_WEAPON_STRING("/MERCURY") " MISSILE"
243 #define WEAPON_STRING_MEGA	"MEGA" D2X_EXTENDED_WEAPON_STRING("/EARTHSHAKER") " MISSILE"
244 
245 #if DXX_MAX_BUTTONS_PER_JOYSTICK || DXX_MAX_HATS_PER_JOYSTICK
246 #define DXX_KCONFIG_ITEM_JOY_WIDTH(I)	I
247 #else
248 #define DXX_KCONFIG_ITEM_JOY_WIDTH(I)	(static_cast<void>(I), 0)
249 #endif
250 
251 #include "kconfig.ui-table.cpp"
252 
253 static enumerated_array<kc_mitem, std::size(kc_keyboard), dxx_kconfig_ui_kc_keyboard> kcm_keyboard;
254 #if DXX_MAX_JOYSTICKS
255 static enumerated_array<kc_mitem, std::size(kc_joystick), dxx_kconfig_ui_kc_joystick> kcm_joystick;
256 #endif
257 static enumerated_array<kc_mitem, std::size(kc_mouse), dxx_kconfig_ui_kc_mouse> kcm_mouse;
258 static std::array<kc_mitem, std::size(kc_rebirth)> kcm_rebirth;
259 
kconfig_start_changing(kc_menu & menu)260 static void kconfig_start_changing(kc_menu &menu)
261 {
262 	const auto citem = menu.citem;
263 	if (menu.items[citem].type == BT_INVERT)
264 	{
265 		menu.changing = 0;		// in case we were changing something else
266 		auto &value = menu.mitems[citem].value;
267 		value = value ? 0 : 1;
268 		return;
269 	}
270 	menu.q_fade_i = 0;	// start question mark flasher
271 	menu.changing = 1;
272 }
273 
kc_set_exclusive_binding(kc_menu & menu,kc_mitem & mitem,const unsigned type,const unsigned value)274 static void kc_set_exclusive_binding(kc_menu &menu, kc_mitem &mitem, const unsigned type, const unsigned value)
275 {
276 	const auto nitems = menu.nitems;
277 	for (auto &&[iterate_mitem, iterate_item] : zip(unchecked_partial_range(menu.mitems, nitems), unchecked_partial_range(menu.items, nitems)))
278 	{
279 		if (&iterate_mitem != &mitem && iterate_mitem.value == value && iterate_item.type == type)
280 			iterate_mitem.value = 255;
281 	}
282 	mitem.value = value;
283 	menu.changing = 0;
284 }
285 
286 }
287 }
288 
289 static void kc_drawinput(grs_canvas &, const grs_font &, const kc_item &item, kc_mitem &mitem, int is_current, const char *label);
290 static void kc_change_key( kc_menu &menu,const d_event &event, kc_mitem& mitem );
291 #if DXX_MAX_BUTTONS_PER_JOYSTICK || DXX_MAX_HATS_PER_JOYSTICK
292 static void kc_change_joybutton( kc_menu &menu,const d_event &event, kc_mitem& mitem );
293 #endif
294 static void kc_change_mousebutton( kc_menu &menu,const d_event &event, kc_mitem& mitem );
295 #if DXX_MAX_AXES_PER_JOYSTICK
296 static void kc_change_joyaxis( kc_menu &menu,const d_event &event, kc_mitem& mitem );
297 #endif
298 static void kc_change_mouseaxis( kc_menu &menu,const d_event &event, kc_mitem& mitem );
299 
get_item_text(const kc_item & item,const kc_mitem & mitem,char (& buf)[10])300 static const char *get_item_text(const kc_item &item, const kc_mitem &mitem, char (&buf)[10])
301 {
302 	if (mitem.value==255) {
303 		return "";
304 	} else {
305 		switch( item.type )	{
306 			case BT_KEY:
307 				return key_properties[mitem.value].key_text;
308 			case BT_MOUSE_BUTTON:
309 				return mousebutton_text[mitem.value];
310 			case BT_MOUSE_AXIS:
311 				return mouseaxis_text[mitem.value];
312 #if DXX_MAX_BUTTONS_PER_JOYSTICK || DXX_MAX_HATS_PER_JOYSTICK
313 			case BT_JOY_BUTTON:
314 				if (joybutton_text.size() > mitem.value)
315 					return &joybutton_text[mitem.value][0];
316 				else
317 				{
318 					snprintf(buf, sizeof(buf), "BTN%2d", mitem.value + 1);
319 					return buf;
320 				}
321 				break;
322 #else
323 				(void)buf;
324 #endif
325 #if DXX_MAX_AXES_PER_JOYSTICK
326 			case BT_JOY_AXIS:
327 				if (joyaxis_text.size() > mitem.value)
328 					return &joyaxis_text[mitem.value][0];
329 				else
330 				{
331 					snprintf(buf, sizeof(buf), "AXIS%2d", mitem.value + 1);
332 					return buf;
333 				}
334 				break;
335 #else
336 				(void)buf;
337 #endif
338 			case BT_INVERT:
339 				return invert_text[mitem.value];
340 			default:
341 				return NULL;
342 		}
343 	}
344 }
345 
get_item_height(const grs_font & cv_font,const kc_item & item,const kc_mitem & mitem)346 static int get_item_height(const grs_font &cv_font, const kc_item &item, const kc_mitem &mitem)
347 {
348 	char buf[10];
349 	const char *btext;
350 
351 	btext = get_item_text(item, mitem, buf);
352 	if (!btext)
353 		return 0;
354 	const auto h = gr_get_string_size(cv_font, btext).height;
355 	return h;
356 }
357 
kc_gr_2y_string(grs_canvas & canvas,const grs_font & cv_font,const char * const s,const font_y_scaled_float y,const font_x_scaled_float x0,const font_x_scaled_float x1)358 static void kc_gr_2y_string(grs_canvas &canvas, const grs_font &cv_font, const char *const s, const font_y_scaled_float y, const font_x_scaled_float x0, const font_x_scaled_float x1)
359 {
360 	const auto &&[w, h] = gr_get_string_size(cv_font, s);
361 	gr_string(canvas, cv_font, x0, y, s, w, h);
362 	gr_string(canvas, cv_font, x1, y, s, w, h);
363 }
364 
kconfig_draw(kc_menu & menu)365 static void kconfig_draw(kc_menu &menu)
366 {
367 	grs_canvas &save_canvas = *grd_curcanv;
368 	const auto &&fspacx = FSPACX();
369 	const auto &&fspacy = FSPACY();
370 	int w = menu.w_canv.cv_bitmap.bm_w, h = menu.w_canv.cv_bitmap.bm_h;
371 
372 	gr_set_default_canvas();
373 	nm_draw_background(*grd_curcanv, ((SWIDTH - w) / 2) - BORDERX, ((SHEIGHT - h) / 2) - BORDERY, ((SWIDTH - w) / 2) + w + BORDERX, ((SHEIGHT - h) / 2) + h + BORDERY);
374 
375 	gr_set_current_canvas(menu.w_canv);
376 	auto &canvas = *grd_curcanv;
377 
378 	const grs_font *save_font = canvas.cv_font;
379 
380 	auto &medium3_font = *MEDIUM3_FONT;
381 	gr_string(canvas, medium3_font, 0x8000, fspacy(8), menu.title);
382 
383 	auto &game_font = *GAME_FONT;
384 	gr_set_fontcolor(canvas, BM_XRGB(28, 28, 28), -1);
385 	gr_string(canvas, game_font, 0x8000, fspacy(21), "Enter changes, ctrl-d deletes, ctrl-r resets defaults, ESC exits");
386 
387 	if (menu.items == kc_keyboard)
388 	{
389 		gr_set_fontcolor(canvas, BM_XRGB(31, 27, 6), -1);
390 		const uint8_t color = BM_XRGB(31,27,6);
391 		const auto &&fspacx98 = fspacx(98);
392 		const auto &&fspacx128 = fspacx(128);
393 		const auto &&fspacy42 = fspacy(42);
394 		gr_rect(canvas, fspacx98, fspacy42, fspacx(106), fspacy42, color); // horiz/left
395 		gr_rect(canvas, fspacx(120), fspacy42, fspacx128, fspacy42, color); // horiz/right
396 		const auto &&fspacy44 = fspacy(44);
397 		gr_rect(canvas, fspacx98, fspacy42, fspacx98, fspacy44, color); // vert/left
398 		gr_rect(canvas, fspacx128, fspacy42, fspacx128, fspacy44, color); // vert/right
399 
400 		const auto &&fspacx253 = fspacx(253);
401 		const auto &&fspacx283 = fspacx(283);
402 		gr_rect(canvas, fspacx253, fspacy42, fspacx(261), fspacy42, color); // horiz/left
403 		gr_rect(canvas, fspacx(275), fspacy42, fspacx283, fspacy42, color); // horiz/right
404 		gr_rect(canvas, fspacx253, fspacy42, fspacx253, fspacy44, color); // vert/left
405 		gr_rect(canvas, fspacx283, fspacy42, fspacx283, fspacy44, color); // vert/right
406 
407 		kc_gr_2y_string(canvas, game_font, "OR", fspacy(40), fspacx(109), fspacx(264));
408 	}
409 #if DXX_MAX_JOYSTICKS
410 	else if (menu.items == kc_joystick)
411 	{
412 		const uint8_t color = BM_XRGB(31, 27, 6);
413 		gr_set_fontcolor(canvas, color, -1);
414 #if DXX_MAX_AXES_PER_JOYSTICK
415 		constexpr auto kconfig_axis_labels_top_y = DXX_KCONFIG_UI_JOYSTICK_AXES_TOP_Y + 8;
416 		const auto &&fspacy_lower_label2 = fspacy(kconfig_axis_labels_top_y);
417 		gr_string(canvas, game_font, 0x8000, fspacy(DXX_KCONFIG_UI_JOYSTICK_AXES_TOP_Y), TXT_AXES);
418 		gr_set_fontcolor(canvas, BM_XRGB(28, 28, 28), -1);
419 		kc_gr_2y_string(canvas, game_font, TXT_AXIS, fspacy_lower_label2, fspacx(81), fspacx(230));
420 		kc_gr_2y_string(canvas, game_font, TXT_INVERT, fspacy_lower_label2, fspacx(111), fspacx(260));
421 		gr_set_fontcolor(canvas, color, -1);
422 #endif
423 
424 #if DXX_MAX_BUTTONS_PER_JOYSTICK || DXX_MAX_HATS_PER_JOYSTICK
425 		constexpr auto kconfig_buttons_top_y = DXX_KCONFIG_UI_JOYSTICK_TOP_Y + 6;
426 		constexpr auto kconfig_buttons_labels_top_y = kconfig_buttons_top_y + 4;
427 		gr_string(canvas, game_font, 0x8000, fspacy(DXX_KCONFIG_UI_JOYSTICK_TOP_Y), TXT_BUTTONS);
428 		const auto &&fspacx115 = fspacx(115);
429 		const auto &&fspacy_horiz_OR_line = fspacy(kconfig_buttons_labels_top_y);
430 		gr_rect(canvas, fspacx115, fspacy_horiz_OR_line, fspacx(123), fspacy_horiz_OR_line, color); // horiz/left
431 		const auto &&fspacx145 = fspacx(145);
432 		gr_rect(canvas, fspacx(137), fspacy_horiz_OR_line, fspacx145, fspacy_horiz_OR_line, color); // horiz/right
433 		const auto &&fspacx261 = fspacx(261);
434 		gr_rect(canvas, fspacx261, fspacy_horiz_OR_line, fspacx(269), fspacy_horiz_OR_line, color); // horiz/left
435 		const auto &&fspacx291 = fspacx(291);
436 		gr_rect(canvas, fspacx(283), fspacy_horiz_OR_line, fspacx291, fspacy_horiz_OR_line, color); // horiz/right
437 
438 		const auto &&fspacy_vert_OR_line = fspacy(kconfig_buttons_labels_top_y + 2);
439 		gr_rect(canvas, fspacx115, fspacy_horiz_OR_line, fspacx115, fspacy_vert_OR_line, color); // vert/left
440 		gr_rect(canvas, fspacx145, fspacy_horiz_OR_line, fspacx145, fspacy_vert_OR_line, color); // vert/right
441 		gr_rect(canvas, fspacx261, fspacy_horiz_OR_line, fspacx261, fspacy_vert_OR_line, color); // vert/left
442 		gr_rect(canvas, fspacx291, fspacy_horiz_OR_line, fspacx291, fspacy_vert_OR_line, color); // vert/right
443 
444 		kc_gr_2y_string(canvas, game_font, "OR", fspacy(kconfig_buttons_top_y + 2), fspacx(126), fspacx(272));
445 #endif
446 	}
447 #endif
448 	else if (menu.items == kc_mouse)
449 	{
450 		gr_set_fontcolor(canvas, BM_XRGB(31, 27, 6), -1);
451 		gr_string(canvas, game_font, 0x8000, fspacy(37), TXT_BUTTONS);
452 		gr_string(canvas, game_font, 0x8000, fspacy(137), TXT_AXES);
453 		gr_set_fontcolor(canvas, BM_XRGB(28, 28, 28), -1);
454 		const auto &&fspacy145 = fspacy(145);
455 		kc_gr_2y_string(canvas, game_font, TXT_AXIS, fspacy145, fspacx( 87), fspacx(242));
456 		kc_gr_2y_string(canvas, game_font, TXT_INVERT, fspacy145, fspacx(120), fspacx(274));
457 	}
458 	else if (menu.items == kc_rebirth)
459 	{
460 		gr_set_fontcolor(canvas, BM_XRGB(31, 27, 6), -1);
461 
462 		const auto &&fspacy60 = fspacy(60);
463 		gr_string(canvas, game_font, fspacx(152), fspacy60, "KEYBOARD");
464 #if DXX_MAX_BUTTONS_PER_JOYSTICK || DXX_MAX_HATS_PER_JOYSTICK
465 		gr_string(canvas, game_font, fspacx(210), fspacy60, "JOYSTICK");
466 #endif
467 		gr_string(canvas, game_font, fspacx(273), fspacy60, "MOUSE");
468 	}
469 
470 	unsigned citem = menu.citem;
471 	const char *current_label = NULL;
472 	const char *litem = menu.litems;
473 	const auto nitems = menu.nitems;
474 	for (const auto i : xrange(nitems))
475 	{
476 		auto next_label = (i + 1 >= menu.nitems || menu.items[i + 1].y != menu.items[i].y) ? litem : nullptr;
477 		if (i == citem)
478 			current_label = litem;
479 		else if (menu.items[i].w2)
480 			kc_drawinput(canvas, game_font, menu.items[i], menu.mitems[i], 0, next_label);
481 		if (next_label)
482 			litem += strlen(litem) + 1;
483 	}
484 	kc_drawinput(canvas, game_font, menu.items[citem], menu.mitems[citem], 1, current_label);
485 
486 	gr_set_fontcolor(canvas, BM_XRGB(28, 28, 28), -1);
487 	if (menu.changing)
488 	{
489 		const char *s;
490 		switch(menu.items[menu.citem].type)
491 		{
492 			case BT_KEY:
493 				s = TXT_PRESS_NEW_KEY;
494 				break;
495 			case BT_MOUSE_BUTTON:
496 				s = TXT_PRESS_NEW_MBUTTON;
497 				break;
498 			case BT_MOUSE_AXIS:
499 				s = TXT_MOVE_NEW_MSE_AXIS;
500 				break;
501 #if DXX_MAX_BUTTONS_PER_JOYSTICK || DXX_MAX_HATS_PER_JOYSTICK
502 			case BT_JOY_BUTTON:
503 				s = TXT_PRESS_NEW_JBUTTON;
504 				break;
505 #endif
506 #if DXX_MAX_AXES_PER_JOYSTICK
507 			case BT_JOY_AXIS:
508 				s = TXT_MOVE_NEW_JOY_AXIS;
509 				break;
510 #endif
511 			default:
512 				s = nullptr;
513 				break;
514 		}
515 		if (s)
516 			gr_string(canvas, game_font, 0x8000, fspacy(INFO_Y), s);
517 		auto &item = menu.items[menu.citem];
518 		auto &menu_fade_index = menu.q_fade_i;
519 		const auto fade_element = fades[menu_fade_index];
520 #if defined(DXX_BUILD_DESCENT_I)
521 		const auto color = gr_fade_table[static_cast<gr_fade_level>(fade_element)][BM_XRGB(21, 0, 24)];
522 #elif defined(DXX_BUILD_DESCENT_II)
523 		const auto color = BM_XRGB(21 * fade_element / 31, 0, 24 * fade_element / 31);
524 #endif
525 		kc_drawquestion(canvas, game_font, menu_fade_index, item.xinput, item.y, item.w2, color);
526 	}
527 	canvas.cv_font = save_font;
528 	gr_set_current_canvas( save_canvas );
529 }
530 
in_bounds(unsigned mx,unsigned my,unsigned x1,unsigned xw,unsigned y1,unsigned yh)531 static inline int in_bounds(unsigned mx, unsigned my, unsigned x1, unsigned xw, unsigned y1, unsigned yh)
532 {
533 	if (mx <= x1)
534 		return 0;
535 	if (my <= y1)
536 		return 0;
537 	if (mx >= x1 + xw)
538 		return 0;
539 	if (my >= y1 + yh)
540 		return 0;
541 	return 1;
542 }
543 
544 namespace dsx {
545 
546 namespace {
547 
kconfig_mouse(kc_menu & menu,const d_event & event)548 static window_event_result kconfig_mouse(kc_menu &menu, const d_event &event)
549 {
550 	grs_canvas &save_canvas = *grd_curcanv;
551 	int mx, my, mz, x1, y1;
552 	window_event_result rval = window_event_result::ignored;
553 
554 	gr_set_current_canvas(menu.w_canv);
555 	auto &canvas = *grd_curcanv;
556 
557 	if (menu.mouse_state)
558 	{
559 		int item_height;
560 
561 		mouse_get_pos(&mx, &my, &mz);
562 		const auto &&fspacx = FSPACX();
563 		const auto &&fspacy = FSPACY();
564 		const auto nitems = menu.nitems;
565 		for (const auto i : xrange(nitems))
566 		{
567 			item_height = get_item_height(*canvas.cv_font, menu.items[i], menu.mitems[i]);
568 			x1 = canvas.cv_bitmap.bm_x + fspacx(menu.items[i].xinput);
569 			y1 = canvas.cv_bitmap.bm_y + fspacy(menu.items[i].y);
570 			if (in_bounds(mx, my, x1, fspacx(menu.items[i].w2), y1, item_height)) {
571 				menu.citem = i;
572 				rval = window_event_result::handled;
573 				break;
574 			}
575 		}
576 	}
577 	else if (event.type == EVENT_MOUSE_BUTTON_UP)
578 	{
579 		int item_height;
580 
581 		mouse_get_pos(&mx, &my, &mz);
582 		item_height = get_item_height(*canvas.cv_font, menu.items[menu.citem], menu.mitems[menu.citem]);
583 		const auto &&fspacx = FSPACX();
584 		x1 = canvas.cv_bitmap.bm_x + fspacx(menu.items[menu.citem].xinput);
585 		y1 = canvas.cv_bitmap.bm_y + FSPACY(menu.items[menu.citem].y);
586 		if (in_bounds(mx, my, x1, fspacx(menu.items[menu.citem].w2), y1, item_height)) {
587 			kconfig_start_changing(menu);
588 			rval = window_event_result::handled;
589 		}
590 		else
591 		{
592 			// Click out of changing mode - kreatordxx
593 			menu.changing = 0;
594 			rval = window_event_result::handled;
595 		}
596 	}
597 
598 	gr_set_current_canvas(save_canvas);
599 
600 	return rval;
601 }
602 
603 }
604 
605 }
606 
607 namespace dcx {
608 
609 namespace {
610 
611 template <std::size_t M, std::size_t C>
reset_mitem_values(std::array<kc_mitem,M> & m,const std::array<uint8_t,C> & c)612 static void reset_mitem_values(std::array<kc_mitem, M> &m, const std::array<uint8_t, C> &c)
613 {
614 	for (std::size_t i = 0; i != min(M, C); ++i)
615 		m[i].value = c[i];
616 }
617 
step_citem_past_empty_cell(unsigned & citem,const kc_item * const items,const uint8_t kc_item::* const next)618 static void step_citem_past_empty_cell(unsigned &citem, const kc_item *const items, const uint8_t kc_item::*const next)
619 {
620 	do {
621 		citem = items[citem].*next;
622 	} while (!items[citem].w2);
623 }
624 
kconfig_key_command(kc_menu & menu,const d_event & event)625 static window_event_result kconfig_key_command(kc_menu &menu, const d_event &event)
626 {
627 	auto k = event_key_get(event);
628 
629 	// when changing, process no keys instead of ESC
630 	if (menu.changing && (k != -2 && k != KEY_ESC))
631 		return window_event_result::ignored;
632 	switch (k)
633 	{
634 		case KEY_CTRLED+KEY_D:
635 			menu.mitems[menu.citem].value = 255;
636 			return window_event_result::handled;
637 		case KEY_CTRLED+KEY_R:
638 			if (menu.items==kc_keyboard)
639 				reset_mitem_values(kcm_keyboard, DefaultKeySettings.Keyboard);
640 #if DXX_MAX_JOYSTICKS
641 			else if (menu.items == kc_joystick)
642 				reset_mitem_values(kcm_joystick, DefaultKeySettings.Joystick);
643 #endif
644 			else if (menu.items == kc_mouse)
645 				reset_mitem_values(kcm_mouse, DefaultKeySettings.Mouse);
646 			else if (menu.items == kc_rebirth)
647 				reset_mitem_values(kcm_rebirth, DefaultKeySettingsRebirth);
648 			return window_event_result::handled;
649 		case KEY_DELETE:
650 			menu.mitems[menu.citem].value = 255;
651 			return window_event_result::handled;
652 		case KEY_UP:
653 		case KEY_PAD8:
654 			step_citem_past_empty_cell(menu.citem, menu.items, &kc_item::u);
655 			return window_event_result::handled;
656 		case KEY_DOWN:
657 		case KEY_PAD2:
658 			step_citem_past_empty_cell(menu.citem, menu.items, &kc_item::d);
659 			return window_event_result::handled;
660 		case KEY_LEFT:
661 		case KEY_PAD4:
662 			step_citem_past_empty_cell(menu.citem, menu.items, &kc_item::l);
663 			return window_event_result::handled;
664 		case KEY_RIGHT:
665 		case KEY_PAD6:
666 			step_citem_past_empty_cell(menu.citem, menu.items, &kc_item::r);
667 			return window_event_result::handled;
668 		case KEY_ENTER:
669 		case KEY_PADENTER:
670 			kconfig_start_changing(menu);
671 			return window_event_result::handled;
672 		case KEY_ESC:
673 			if (menu.changing)
674 				menu.changing = 0;
675 			else
676 			{
677 				return window_event_result::close;
678 			}
679 			return window_event_result::handled;
680 		case 0:		// some other event
681 			break;
682 
683 		default:
684 			break;
685 	}
686 	return window_event_result::ignored;
687 }
688 
689 }
690 
691 }
692 
693 namespace dsx {
694 
695 namespace {
696 
event_handler(const d_event & event)697 window_event_result kc_menu::event_handler(const d_event &event)
698 {
699 #if DXX_MAX_BUTTONS_PER_JOYSTICK
700 	if (!changing && joy_translate_menu_key(event))
701 		return window_event_result::handled;
702 #endif
703 
704 	switch (event.type)
705 	{
706 		case EVENT_WINDOW_ACTIVATED:
707 			game_flush_inputs(Controls);
708 			break;
709 
710 		case EVENT_WINDOW_DEACTIVATED:
711 			mouse_state = 0;
712 			break;
713 
714 		case EVENT_MOUSE_BUTTON_DOWN:
715 		case EVENT_MOUSE_BUTTON_UP:
716 			if (changing && (items[citem].type == BT_MOUSE_BUTTON) && (event.type == EVENT_MOUSE_BUTTON_UP))
717 			{
718 				kc_change_mousebutton(*this, event, mitems[citem]);
719 				mouse_state = (event.type == EVENT_MOUSE_BUTTON_DOWN);
720 				return window_event_result::handled;
721 			}
722 
723 			if (event_mouse_get_button(event) == MBTN_RIGHT)
724 			{
725 				if (!changing)
726 				{
727 					return window_event_result::close;
728 				}
729 				return window_event_result::handled;
730 			}
731 			else if (event_mouse_get_button(event) != MBTN_LEFT)
732 				return window_event_result::ignored;
733 
734 			mouse_state = (event.type == EVENT_MOUSE_BUTTON_DOWN);
735 			return kconfig_mouse(*this, event);
736 
737 		case EVENT_MOUSE_MOVED:
738 			if (changing && items[citem].type == BT_MOUSE_AXIS)
739 				kc_change_mouseaxis(*this, event, mitems[citem]);
740 			else
741 				event_mouse_get_delta(event, &old_maxis[0], &old_maxis[1], &old_maxis[2]);
742 			break;
743 
744 #if DXX_MAX_BUTTONS_PER_JOYSTICK || DXX_MAX_HATS_PER_JOYSTICK
745 		case EVENT_JOYSTICK_BUTTON_DOWN:
746 			if (changing && items[citem].type == BT_JOY_BUTTON)
747 				kc_change_joybutton(*this, event, mitems[citem]);
748 			break;
749 #endif
750 
751 #if DXX_MAX_AXES_PER_JOYSTICK
752 		case EVENT_JOYSTICK_MOVED:
753 			if (changing && items[citem].type == BT_JOY_AXIS)
754 				kc_change_joyaxis(*this, event, mitems[citem]);
755 			else
756 			{
757 				const auto &av = event_joystick_get_axis(event);
758 				const auto &axis = av.axis;
759 				const auto &value = av.value;
760 				old_jaxis[axis] = value;
761 			}
762 			break;
763 #endif
764 
765 		case EVENT_KEY_COMMAND:
766 		{
767 			window_event_result rval = kconfig_key_command(*this, event);
768 			if (rval != window_event_result::ignored)
769 				return rval;
770 			if (changing && items[citem].type == BT_KEY)
771 				kc_change_key(*this, event, mitems[citem]);
772 			return window_event_result::ignored;
773 		}
774 
775 		case EVENT_IDLE:
776 			kconfig_mouse(*this, event);
777 			break;
778 
779 		case EVENT_WINDOW_DRAW:
780 			if (changing)
781 				timer_delay(f0_1/10);
782 			else
783 				timer_delay2(50);
784 			kconfig_draw(*this);
785 			break;
786 
787 		case EVENT_WINDOW_CLOSE:
788 			// Update save values...
789 			for (auto &&[kcm, setting] : zip(kcm_keyboard, PlayerCfg.KeySettings.Keyboard))
790 				setting = kcm.value;
791 #if DXX_MAX_JOYSTICKS
792 			for (auto &&[kcm, setting] : zip(kcm_joystick, PlayerCfg.KeySettings.Joystick))
793 				setting = kcm.value;
794 #endif
795 
796 			for (auto &&[kcm, setting] : zip(kcm_mouse, PlayerCfg.KeySettings.Mouse))
797 				setting = kcm.value;
798 			for (auto &&[kcm, setting] : zip(kcm_rebirth, PlayerCfg.KeySettingsRebirth))
799 				setting = kcm.value;
800 			return window_event_result::ignored;	// continue closing
801 		default:
802 			return window_event_result::ignored;
803 	}
804 	return window_event_result::handled;
805 }
806 
kconfig_sub(const char * litems,const kc_item * items,kc_mitem * mitems,int nitems,const char * title)807 static void kconfig_sub(const char *litems, const kc_item * items,kc_mitem *mitems,int nitems, const char *title)
808 {
809 	set_screen_mode(SCREEN_MENU);
810 	kc_set_controls();
811 
812 	const auto &&fspacx = FSPACX();
813 	const auto &&fspacy = FSPACY();
814 	const uint16_t target_window_width = fspacx(320);
815 	const uint16_t target_window_height = fspacy(220);
816 	const auto swidth = SWIDTH;
817 	const auto sheight = SHEIGHT;
818 	const auto window_width = std::min(swidth, target_window_width);
819 	const auto window_height = std::min(sheight, target_window_height);
820 	auto menu = window_create<kc_menu>(grd_curscreen->sc_canvas, (swidth - window_width) / 2, (sheight - window_height) / 2, window_width, window_height);
821 	menu->items = items;
822 	menu->litems = litems;
823 	menu->mitems = mitems;
824 	menu->nitems = nitems;
825 	menu->title = title;
826 	if (!items[0].w2)
827 		step_citem_past_empty_cell(menu->citem, items, &kc_item::r);
828 }
829 
830 template <std::size_t N>
kconfig_sub(const char * litems,const kc_item (& items)[N],std::array<kc_mitem,N> & mitems,const char * title)831 static void kconfig_sub(const char *litems, const kc_item (&items)[N], std::array<kc_mitem, N> &mitems, const char *title)
832 {
833 	kconfig_sub(litems, items, mitems.data(), N, title);
834 }
835 
836 }
837 
838 }
839 
kc_drawinput(grs_canvas & canvas,const grs_font & cv_font,const kc_item & item,kc_mitem & mitem,const int is_current,const char * const label)840 static void kc_drawinput(grs_canvas &canvas, const grs_font &cv_font, const kc_item &item, kc_mitem &mitem, const int is_current, const char *const label)
841 {
842 	char buf[10];
843 	const char *btext;
844 	const auto &&fspacx = FSPACX();
845 	const auto &&fspacy = FSPACY();
846 	unsigned r, g, b;
847 	if (label)
848 	{
849 		if (is_current)
850 			r = 20 * 2, g = 20 * 2, b = 29 * 2;
851 		else
852 			r = 15 * 2, g = 15 * 2, b = 24 * 2;
853 		gr_set_fontcolor(canvas, gr_find_closest_color(r, g, b), -1);
854 		gr_string(canvas, cv_font, fspacx(item.x), fspacy(item.y), label);
855 	}
856 
857 	btext = get_item_text(item, mitem, buf);
858 	if (!btext)
859 		return;
860 	{
861 		if (is_current)
862 			r = 21 * 2, g = 0, b = 24 * 2;
863 		else
864 			r = 16 * 2, g = 0, b = 19 * 2;
865 
866 		int x;
867 		const auto color = gr_find_closest_color(r, g, b);
868 		const auto &&[w, h] = gr_get_string_size(cv_font, btext);
869 		const auto &&fspacx_item_xinput = fspacx(item.xinput);
870 		const auto &&fspacy_item_y = fspacy(item.y);
871 		gr_urect(canvas, fspacx_item_xinput, fspacy(item.y - 1), fspacx(item.xinput + item.w2), fspacy_item_y + h, color);
872 
873 		if (mitem.oldvalue == mitem.value)
874 			r = b = 28 * 2;
875 		else
876 			r = b = 0;
877 		gr_set_fontcolor(canvas, gr_find_closest_color(r, 28 * 2, b), -1);
878 
879 		x = fspacx_item_xinput + ((fspacx(item.w2) - w) / 2);
880 
881 		gr_string(canvas, cv_font, x, fspacy_item_y, btext, w, h);
882 	}
883 }
884 
kc_change_key(kc_menu & menu,const d_event & event,kc_mitem & mitem)885 static void kc_change_key( kc_menu &menu,const d_event &event, kc_mitem &mitem )
886 {
887 	ubyte keycode = 255;
888 
889 	Assert(event.type == EVENT_KEY_COMMAND);
890 	keycode = event_key_get_raw(event);
891 
892 	auto e = end(system_keys);
893 	if (unlikely(CGameArg.CtlNoStickyKeys))
894 		e = std::prev(e, 3);
895 	const auto predicate = [keycode](uint8_t k) { return keycode == k; };
896 	if (std::any_of(begin(system_keys), e, predicate))
897 		return;
898 
899 	kc_set_exclusive_binding(menu, mitem, BT_KEY, keycode);
900 }
901 
902 #if DXX_MAX_BUTTONS_PER_JOYSTICK
kc_change_joybutton(kc_menu & menu,const d_event & event,kc_mitem & mitem)903 static void kc_change_joybutton( kc_menu &menu,const d_event &event, kc_mitem &mitem )
904 {
905 	int button = 255;
906 
907 	Assert(event.type == EVENT_JOYSTICK_BUTTON_DOWN);
908 	button = event_joystick_get_button(event);
909 
910 	kc_set_exclusive_binding(menu, mitem, BT_JOY_BUTTON, button);
911 }
912 #endif
913 
kc_change_mousebutton(kc_menu & menu,const d_event & event,kc_mitem & mitem)914 static void kc_change_mousebutton( kc_menu &menu,const d_event &event, kc_mitem &mitem)
915 {
916 	int button;
917 
918 	Assert(event.type == EVENT_MOUSE_BUTTON_DOWN || event.type == EVENT_MOUSE_BUTTON_UP);
919 	button = event_mouse_get_button(event);
920 
921 	kc_set_exclusive_binding(menu, mitem, BT_MOUSE_BUTTON, button);
922 }
923 
924 #if DXX_MAX_AXES_PER_JOYSTICK
kc_change_joyaxis(kc_menu & menu,const d_event & event,kc_mitem & mitem)925 static void kc_change_joyaxis( kc_menu &menu,const d_event &event, kc_mitem &mitem )
926 {
927 	const auto &av = event_joystick_get_axis(event);
928 	const auto &axis = av.axis;
929 	const auto &value = av.value;
930 
931 	if ( abs(value-menu.old_jaxis[axis])<32 )
932 		return;
933 	con_printf(CON_DEBUG, "Axis Movement detected: Axis %i", axis);
934 
935 	kc_set_exclusive_binding(menu, mitem, BT_JOY_AXIS, axis);
936 }
937 #endif
938 
kc_change_mouseaxis(kc_menu & menu,const d_event & event,kc_mitem & mitem)939 static void kc_change_mouseaxis( kc_menu &menu,const d_event &event, kc_mitem &mitem )
940 {
941 	int dx, dy, dz;
942 
943 	Assert(event.type == EVENT_MOUSE_MOVED);
944 	event_mouse_get_delta( event, &dx, &dy, &dz );
945 	uint8_t code;
946 	if (abs(dz) > 5)
947 		code = 2;
948 	else if (abs(dy) > 5)
949 		code = 1;
950 	else if (abs(dx) > 5)
951 		code = 0;
952 	else
953 		return;
954 	kc_set_exclusive_binding(menu, mitem, BT_MOUSE_AXIS, code);
955 }
956 
kconfig(const kconfig_type n)957 void kconfig(const kconfig_type n)
958 {
959 	switch (n)
960 	{
961 #define kconfig_case(TYPE,TITLE)	\
962 		case kconfig_type::TYPE:	\
963 			kconfig_sub(DXX_KCONFIG_UI_LABEL_kc_##TYPE, kc_##TYPE, kcm_##TYPE, TITLE);	\
964 			break;
965 		kconfig_case(keyboard, "KEYBOARD");
966 #if DXX_MAX_JOYSTICKS
967 		kconfig_case(joystick, "JOYSTICK");
968 #endif
969 		kconfig_case(mouse, "MOUSE");
970 		kconfig_case(rebirth, "WEAPON KEYS");
971 #undef kconfig_case
972 		default:
973 			Int3();
974 			return;
975 	}
976 }
977 
978 namespace dsx {
979 
input_button_matched(control_info & Controls,const kc_item & item,int down)980 static void input_button_matched(control_info &Controls, const kc_item& item, int down)
981 {
982 	if (item.state_bit)
983 	{
984 		if (!item.ci_state_ptr)
985 			throw std::logic_error("NULL state pointer with non-zero state bit");
986 		if (down)
987 			Controls.state.*item.ci_state_ptr |= item.state_bit;
988 		else
989 			Controls.state.*item.ci_state_ptr &= ~item.state_bit;
990 	}
991 	else
992 	{
993 		if (item.ci_count_ptr != NULL && down)
994 			Controls.state.*item.ci_count_ptr += 1;
995 	}
996 }
997 
998 }
999 
1000 namespace dcx {
1001 
1002 namespace {
1003 
1004 template <template<typename> class F>
adjust_ramped_keyboard_field(float & keydown_time,ubyte & state,fix & time,const float & sensitivity,const int & speed_factor,const int & speed_divisor=1)1005 static void adjust_ramped_keyboard_field(float& keydown_time, ubyte& state, fix& time, const float& sensitivity, const int& speed_factor, const int& speed_divisor = 1)
1006 #define adjust_ramped_keyboard_field(F, M, ...)	\
1007 	(adjust_ramped_keyboard_field<F>(Controls.down_time.M, Controls.state.M, __VA_ARGS__))
1008 {
1009 	if (state)
1010 	{
1011                 if (keydown_time < F1_0)
1012 			keydown_time += (static_cast<float>(FrameTime) * 6.66) * ((sensitivity + 1) / 17); // values based on observation that the original game uses a keyboard ramp of 8 frames. Full sensitivity should reflect 60FPS behaviour, half sensitivity reflects 30FPS behaviour (give or take a frame).
1013                 time = F<fix>()(time, speed_factor / speed_divisor * (keydown_time / F1_0));
1014 	}
1015 	else
1016 		keydown_time = 0;
1017 }
1018 
1019 template <std::size_t N>
adjust_axis_field(fix & time,const std::array<fix,N> & axes,unsigned value,unsigned invert,const int & sensitivity)1020 static void adjust_axis_field(fix& time, const std::array<fix, N> &axes, unsigned value, unsigned invert, const int& sensitivity)
1021 {
1022 	if (value == 255)
1023 		return;
1024 	fix amount = (axes.at(value) * sensitivity) / 8;
1025 	if ( !invert ) // If not inverted...
1026 		time -= amount;
1027 	else
1028 		time += amount;
1029 }
1030 
clamp_value(fix & value,const fix & lower,const fix & upper)1031 static void clamp_value(fix& value, const fix& lower, const fix& upper)
1032 {
1033 	value = min(max(value, lower), upper);
1034 }
1035 
clamp_symmetric_value(fix & value,const fix & bound)1036 static void clamp_symmetric_value(fix& value, const fix& bound)
1037 {
1038 	clamp_value(value, -bound, bound);
1039 }
1040 
1041 #if DXX_MAX_AXES_PER_JOYSTICK
convert_raw_joy_axis(control_info::joystick_axis_values & Controls,const uint_fast32_t player_cfg_index,const uint_fast32_t i)1042 static void convert_raw_joy_axis(control_info::joystick_axis_values &Controls, const uint_fast32_t player_cfg_index, const uint_fast32_t i)
1043 {
1044 	const auto raw_joy_axis = Controls.raw_joy_axis[i];
1045 	const auto joy_axis = (abs(raw_joy_axis) <= (128 * PlayerCfg.JoystickLinear[player_cfg_index]) / 16)
1046 		? (raw_joy_axis * (FrameTime * PlayerCfg.JoystickSpeed[player_cfg_index]) / 16)
1047 		: (raw_joy_axis * FrameTime);
1048 	Controls.joy_axis[i] = joy_axis / 128;
1049 }
1050 
convert_raw_joy_axis(control_info::joystick_axis_values & Controls,const dxx_kconfig_ui_kc_joystick kcm_index,const uint_fast32_t player_cfg_index,const uint_fast32_t i)1051 static void convert_raw_joy_axis(control_info::joystick_axis_values &Controls, const dxx_kconfig_ui_kc_joystick kcm_index, const uint_fast32_t player_cfg_index, const uint_fast32_t i)
1052 {
1053 	if (i != kcm_joystick[kcm_index].value)
1054 		return;
1055 	convert_raw_joy_axis(Controls, player_cfg_index, i);
1056 }
1057 #endif
1058 
adjust_button_time(fix & o,uint8_t add,uint8_t sub,fix v)1059 static inline void adjust_button_time(fix &o, uint8_t add, uint8_t sub, fix v)
1060 {
1061 	if (add)
1062 	{
1063 		if (sub)
1064 			return;
1065 		o += v;
1066 	}
1067 	else if (sub)
1068 		o -= v;
1069 }
1070 
clamp_kconfig_control_with_overrun(fix & value,const fix & bound,fix & excess,const fix & ebound)1071 static void clamp_kconfig_control_with_overrun(fix &value, const fix &bound, fix &excess, const fix &ebound)
1072 {
1073 	/* Assume no integer overflow here */
1074 	value += excess;
1075 	const auto ivalue = value;
1076 	clamp_symmetric_value(value, bound);
1077 	excess = ivalue - value;
1078 	clamp_symmetric_value(excess, ebound);
1079 }
1080 
allow_uncapped_turning()1081 static unsigned allow_uncapped_turning()
1082 {
1083 	const auto game_mode = Game_mode;
1084 	if (!(game_mode & GM_MULTI))
1085 		return PlayerCfg.MouselookFlags & MouselookMode::Singleplayer;
1086 	return PlayerCfg.MouselookFlags &
1087 		Netgame.MouselookFlags &
1088 		((game_mode & GM_MULTI_COOP)
1089 		? MouselookMode::MPCoop
1090 		: MouselookMode::MPAnarchy);
1091 }
1092 
1093 }
1094 
1095 }
1096 
1097 namespace dsx {
1098 
kconfig_read_controls(control_info & Controls,const d_event & event,int automap_flag)1099 void kconfig_read_controls(control_info &Controls, const d_event &event, int automap_flag)
1100 {
1101 	static fix64 mouse_delta_time = 0;
1102 
1103 #ifndef NDEBUG
1104 	// --- Don't do anything if in debug mode ---
1105 	if ( keyd_pressed[KEY_DELETE] )
1106 	{
1107 		Controls = {};
1108 		return;
1109 	}
1110 #endif
1111 
1112 	const auto frametime = FrameTime;
1113 
1114 	switch (event.type)
1115 	{
1116 		case EVENT_KEY_COMMAND:
1117 		case EVENT_KEY_RELEASE:
1118 			{
1119 				const auto &&key = event_key_get_raw(event);
1120 				if (key < 255)
1121 				{
1122 					for (auto &&[kc, kcm] : zip(kc_keyboard, kcm_keyboard))
1123 					{
1124 						if (kcm.value == key)
1125 							input_button_matched(Controls, kc, event.type == EVENT_KEY_COMMAND);
1126 					}
1127 			if (!automap_flag && event.type == EVENT_KEY_COMMAND)
1128 				for (uint_fast32_t i = 0, j = 0; i < 28; i += 3, j++)
1129 					if (kcm_rebirth[i].value == key)
1130 					{
1131 						Controls.state.select_weapon = j+1;
1132 						break;
1133 					}
1134 				}
1135 			}
1136 			break;
1137 #if DXX_MAX_BUTTONS_PER_JOYSTICK
1138 		case EVENT_JOYSTICK_BUTTON_DOWN:
1139 		case EVENT_JOYSTICK_BUTTON_UP:
1140 			if (!(PlayerCfg.ControlType & CONTROL_USING_JOYSTICK))
1141 				break;
1142 			{
1143 				const auto &&button = event_joystick_get_button(event);
1144 				if (button < 255)
1145 				{
1146 					for (auto &&[kc, kcm] : zip(kc_joystick, kcm_joystick))
1147 					{
1148 						if (kc.type == BT_JOY_BUTTON && kcm.value == button)
1149 							input_button_matched(Controls, kc, event.type == EVENT_JOYSTICK_BUTTON_DOWN);
1150 					}
1151 			if (!automap_flag && event.type == EVENT_JOYSTICK_BUTTON_DOWN)
1152 				for (uint_fast32_t i = 1, j = 0; i < 29; i += 3, j++)
1153 					if (kcm_rebirth[i].value == button)
1154 					{
1155 						Controls.state.select_weapon = j+1;
1156 						break;
1157 					}
1158 				}
1159 			break;
1160 			}
1161 #endif
1162 		case EVENT_MOUSE_BUTTON_DOWN:
1163 		case EVENT_MOUSE_BUTTON_UP:
1164 			if (!(PlayerCfg.ControlType & CONTROL_USING_MOUSE))
1165 				break;
1166 			{
1167 				const auto &&button = event_mouse_get_button(event);
1168 				if (button < 255)
1169 				{
1170 					for (auto &&[kc, kcm] : zip(kc_mouse, kcm_mouse))
1171 					{
1172 						if (kc.type == BT_MOUSE_BUTTON && kcm.value == button)
1173 							input_button_matched(Controls, kc, event.type == EVENT_MOUSE_BUTTON_DOWN);
1174 					}
1175 			if (!automap_flag && event.type == EVENT_MOUSE_BUTTON_DOWN)
1176 				for (uint_fast32_t i = 2, j = 0; i < 30; i += 3, j++)
1177 					if (kcm_rebirth[i].value == button)
1178 					{
1179 						Controls.state.select_weapon = j+1;
1180 						break;
1181 					}
1182 				}
1183 			}
1184 			break;
1185 #if DXX_MAX_AXES_PER_JOYSTICK
1186 		case EVENT_JOYSTICK_MOVED:
1187 		{
1188 			int joy_null_value = 0;
1189 			if (!(PlayerCfg.ControlType & CONTROL_USING_JOYSTICK))
1190 				break;
1191 			const auto &av = event_joystick_get_axis(event);
1192 			const auto &axis = av.axis;
1193 			auto value = av.value;
1194 
1195 			if (axis == PlayerCfg.KeySettings.Joystick[dxx_kconfig_ui_kc_joystick_pitch]) // Pitch U/D Deadzone
1196 				joy_null_value = PlayerCfg.JoystickDead[1]*8;
1197 			else if (axis == PlayerCfg.KeySettings.Joystick[dxx_kconfig_ui_kc_joystick_turn]) // Turn L/R Deadzone
1198 				joy_null_value = PlayerCfg.JoystickDead[0]*8;
1199 			else if (axis == PlayerCfg.KeySettings.Joystick[dxx_kconfig_ui_kc_joystick_slide_lr]) // Slide L/R Deadzone
1200 				joy_null_value = PlayerCfg.JoystickDead[2]*8;
1201 			else if (axis == PlayerCfg.KeySettings.Joystick[dxx_kconfig_ui_kc_joystick_slide_ud]) // Slide U/D Deadzone
1202 				joy_null_value = PlayerCfg.JoystickDead[3]*8;
1203 #ifdef dxx_kconfig_ui_kc_joystick_bank
1204 			else if (axis == PlayerCfg.KeySettings.Joystick[dxx_kconfig_ui_kc_joystick_bank]) // Bank Deadzone
1205 				joy_null_value = PlayerCfg.JoystickDead[4]*8;
1206 #endif
1207 #ifdef dxx_kconfig_ui_kc_joystick_throttle
1208 			else if (axis == PlayerCfg.KeySettings.Joystick[dxx_kconfig_ui_kc_joystick_throttle]) // Throttle - default deadzone
1209 				joy_null_value = PlayerCfg.JoystickDead[5]*3;
1210 #endif
1211 
1212 			Controls.raw_joy_axis[axis] = apply_deadzone(value, joy_null_value);
1213 			break;
1214 		}
1215 #endif
1216 		case EVENT_MOUSE_MOVED:
1217 		{
1218 			if (!(PlayerCfg.ControlType & CONTROL_USING_MOUSE))
1219 				break;
1220 			if (PlayerCfg.MouseFlightSim)
1221 			{
1222 				int ax[3];
1223 				event_mouse_get_delta( event, &ax[0], &ax[1], &ax[2] );
1224 				for (uint_fast32_t i = 0; i <= 2; i++)
1225 				{
1226 					int mouse_null_value = (i==2?16:PlayerCfg.MouseFSDead*8);
1227 					Controls.raw_mouse_axis[i] += ax[i];
1228 					clamp_symmetric_value(Controls.raw_mouse_axis[i], MOUSEFS_DELTA_RANGE);
1229 					if (Controls.raw_mouse_axis[i] > mouse_null_value)
1230 						Controls.mouse_axis[i] = (((Controls.raw_mouse_axis[i] - mouse_null_value) * MOUSEFS_DELTA_RANGE) / (MOUSEFS_DELTA_RANGE - mouse_null_value) * frametime) / MOUSEFS_DELTA_RANGE;
1231 					else if (Controls.raw_mouse_axis[i] < -mouse_null_value)
1232 						Controls.mouse_axis[i] = (((Controls.raw_mouse_axis[i] + mouse_null_value) * MOUSEFS_DELTA_RANGE) / (MOUSEFS_DELTA_RANGE - mouse_null_value) * frametime) / MOUSEFS_DELTA_RANGE;
1233 					else
1234 						Controls.mouse_axis[i] = 0;
1235 				}
1236 			}
1237 			else
1238 			{
1239 				event_mouse_get_delta( event, &Controls.raw_mouse_axis[0], &Controls.raw_mouse_axis[1], &Controls.raw_mouse_axis[2] );
1240 				Controls.mouse_axis[0] = (Controls.raw_mouse_axis[0] * frametime) / 8;
1241 				Controls.mouse_axis[1] = (Controls.raw_mouse_axis[1] * frametime) / 8;
1242 				Controls.mouse_axis[2] = (Controls.raw_mouse_axis[2] * frametime);
1243 				mouse_delta_time = timer_query() + DESIGNATED_GAME_FRAMETIME;
1244 			}
1245 			break;
1246 		}
1247 		case EVENT_IDLE:
1248 		default:
1249 			if (!PlayerCfg.MouseFlightSim && mouse_delta_time < timer_query())
1250 			{
1251 				Controls.mouse_axis[0] = Controls.mouse_axis[1] = Controls.mouse_axis[2] = 0;
1252 				mouse_delta_time = timer_query() + DESIGNATED_GAME_FRAMETIME;
1253 			}
1254 			break;
1255 	}
1256 
1257 #if DXX_MAX_AXES_PER_JOYSTICK
1258 	for (int i = 0; i < JOY_MAX_AXES; i++) {
1259 		convert_raw_joy_axis(Controls, dxx_kconfig_ui_kc_joystick_turn, 0, i); // Turn L/R
1260 		convert_raw_joy_axis(Controls, dxx_kconfig_ui_kc_joystick_pitch, 1, i); // Pitch U/D
1261 		convert_raw_joy_axis(Controls, dxx_kconfig_ui_kc_joystick_slide_lr, 2, i); // Slide L/R
1262 		convert_raw_joy_axis(Controls, dxx_kconfig_ui_kc_joystick_slide_ud, 3, i); // Slide U/D
1263 		convert_raw_joy_axis(Controls, dxx_kconfig_ui_kc_joystick_bank, 4, i); // Bank
1264 		convert_raw_joy_axis(Controls, dxx_kconfig_ui_kc_joystick_throttle, 5, i); // Throttle
1265 	}
1266 #endif
1267 
1268 	const auto speed_factor = (cheats.turbo ? 2 : 1) * frametime;
1269 
1270 	//------------ Read pitch_time -----------
1271 	if ( !Controls.state.slide_on )
1272 	{
1273 		// From keyboard...
1274 		adjust_ramped_keyboard_field(plus, key_pitch_forward, Controls.pitch_time, PlayerCfg.KeyboardSens[1], speed_factor, 2);
1275 		adjust_ramped_keyboard_field(minus, key_pitch_backward, Controls.pitch_time, PlayerCfg.KeyboardSens[1], speed_factor, 2);
1276 		// From joystick...
1277 #ifdef dxx_kconfig_ui_kc_joystick_pitch
1278 		adjust_axis_field(Controls.pitch_time, Controls.joy_axis, kcm_joystick[dxx_kconfig_ui_kc_joystick_pitch].value, kcm_joystick[dxx_kconfig_ui_kc_joystick_invert_pitch].value, PlayerCfg.JoystickSens[1]);
1279 #endif
1280 		// From mouse...
1281 #ifdef dxx_kconfig_ui_kc_mouse_pitch
1282 		adjust_axis_field(Controls.pitch_time, Controls.mouse_axis, kcm_mouse[dxx_kconfig_ui_kc_mouse_pitch].value, kcm_mouse[dxx_kconfig_ui_kc_mouse_invert_pitch].value, PlayerCfg.MouseSens[1]);
1283 #endif
1284 	}
1285 	else Controls.pitch_time = 0;
1286 
1287 
1288 	//----------- Read vertical_thrust_time -----------------
1289 	if ( Controls.state.slide_on )
1290 	{
1291 		// From keyboard...
1292 		adjust_ramped_keyboard_field(plus, key_pitch_forward, Controls.vertical_thrust_time, PlayerCfg.KeyboardSens[3], speed_factor);
1293 		adjust_ramped_keyboard_field(minus, key_pitch_backward, Controls.vertical_thrust_time, PlayerCfg.KeyboardSens[3], speed_factor);
1294 		// From joystick...
1295 		// NOTE: Use Slide U/D invert setting
1296 #if defined(dxx_kconfig_ui_kc_joystick_pitch) && defined(dxx_kconfig_ui_kc_joystick_slide_ud)
1297 		adjust_axis_field(Controls.vertical_thrust_time, Controls.joy_axis, kcm_joystick[dxx_kconfig_ui_kc_joystick_pitch].value, !kcm_joystick[dxx_kconfig_ui_kc_joystick_invert_slide_ud].value, PlayerCfg.JoystickSens[3]);
1298 #endif
1299 		// From mouse...
1300 		adjust_axis_field(Controls.vertical_thrust_time, Controls.mouse_axis, kcm_mouse[dxx_kconfig_ui_kc_mouse_pitch].value, kcm_mouse[dxx_kconfig_ui_kc_mouse_invert_slide_ud].value, PlayerCfg.MouseSens[3]);
1301 	}
1302 	// From keyboard...
1303 	adjust_ramped_keyboard_field(plus, key_slide_up, Controls.vertical_thrust_time, PlayerCfg.KeyboardSens[3], speed_factor);
1304 	adjust_ramped_keyboard_field(minus, key_slide_down, Controls.vertical_thrust_time, PlayerCfg.KeyboardSens[3], speed_factor);
1305 	// From buttons...
1306 	adjust_button_time(Controls.vertical_thrust_time, Controls.state.btn_slide_up, Controls.state.btn_slide_down, speed_factor);
1307 	// From joystick...
1308 #ifdef dxx_kconfig_ui_kc_joystick_slide_ud
1309 	adjust_axis_field(Controls.vertical_thrust_time, Controls.joy_axis, kcm_joystick[dxx_kconfig_ui_kc_joystick_slide_ud].value, !kcm_joystick[dxx_kconfig_ui_kc_joystick_invert_slide_ud].value, PlayerCfg.JoystickSens[3]);
1310 #endif
1311 	// From mouse...
1312 	adjust_axis_field(Controls.vertical_thrust_time, Controls.mouse_axis, kcm_mouse[dxx_kconfig_ui_kc_mouse_slide_ud].value, !kcm_mouse[dxx_kconfig_ui_kc_mouse_invert_slide_ud].value, PlayerCfg.MouseSens[3]);
1313 
1314 	//---------- Read heading_time -----------
1315 	if (!Controls.state.slide_on && !Controls.state.bank_on)
1316 	{
1317 		// From keyboard...
1318 		adjust_ramped_keyboard_field(plus, key_heading_right, Controls.heading_time, PlayerCfg.KeyboardSens[0], speed_factor);
1319 		adjust_ramped_keyboard_field(minus, key_heading_left, Controls.heading_time, PlayerCfg.KeyboardSens[0], speed_factor);
1320 		// From joystick...
1321 #ifdef dxx_kconfig_ui_kc_joystick_turn
1322 		adjust_axis_field(Controls.heading_time, Controls.joy_axis, kcm_joystick[dxx_kconfig_ui_kc_joystick_turn].value, !kcm_joystick[dxx_kconfig_ui_kc_joystick_invert_turn].value, PlayerCfg.JoystickSens[0]);
1323 #endif
1324 		// From mouse...
1325 		adjust_axis_field(Controls.heading_time, Controls.mouse_axis, kcm_mouse[dxx_kconfig_ui_kc_mouse_turn].value, !kcm_mouse[dxx_kconfig_ui_kc_mouse_invert_turn].value, PlayerCfg.MouseSens[0]);
1326 	}
1327 	else Controls.heading_time = 0;
1328 
1329 	//----------- Read sideways_thrust_time -----------------
1330 	if ( Controls.state.slide_on )
1331 	{
1332 		// From keyboard...
1333 		adjust_ramped_keyboard_field(plus, key_heading_right, Controls.sideways_thrust_time, PlayerCfg.KeyboardSens[2], speed_factor);
1334 		adjust_ramped_keyboard_field(minus, key_heading_left, Controls.sideways_thrust_time, PlayerCfg.KeyboardSens[2], speed_factor);
1335 		// From joystick...
1336 #if defined(dxx_kconfig_ui_kc_joystick_turn) && defined(dxx_kconfig_ui_kc_joystick_slide_lr)
1337 		adjust_axis_field(Controls.sideways_thrust_time, Controls.joy_axis, kcm_joystick[dxx_kconfig_ui_kc_joystick_turn].value, !kcm_joystick[dxx_kconfig_ui_kc_joystick_invert_slide_lr].value, PlayerCfg.JoystickSens[2]);
1338 #endif
1339 		// From mouse...
1340 		adjust_axis_field(Controls.sideways_thrust_time, Controls.mouse_axis, kcm_mouse[dxx_kconfig_ui_kc_mouse_turn].value, !kcm_mouse[dxx_kconfig_ui_kc_mouse_invert_slide_lr].value, PlayerCfg.MouseSens[2]);
1341 	}
1342 	// From keyboard...
1343 	adjust_ramped_keyboard_field(plus, key_slide_right, Controls.sideways_thrust_time, PlayerCfg.KeyboardSens[2], speed_factor);
1344 	adjust_ramped_keyboard_field(minus, key_slide_left, Controls.sideways_thrust_time, PlayerCfg.KeyboardSens[2], speed_factor);
1345 	// From buttons...
1346 	adjust_button_time(Controls.sideways_thrust_time, Controls.state.btn_slide_right, Controls.state.btn_slide_left, speed_factor);
1347 	// From joystick...
1348 #ifdef dxx_kconfig_ui_kc_joystick_slide_lr
1349 	adjust_axis_field(Controls.sideways_thrust_time, Controls.joy_axis, kcm_joystick[dxx_kconfig_ui_kc_joystick_slide_lr].value, !kcm_joystick[dxx_kconfig_ui_kc_joystick_invert_slide_lr].value, PlayerCfg.JoystickSens[2]);
1350 #endif
1351 	// From mouse...
1352 	adjust_axis_field(Controls.sideways_thrust_time, Controls.mouse_axis, kcm_mouse[dxx_kconfig_ui_kc_mouse_slide_lr].value, !kcm_mouse[dxx_kconfig_ui_kc_mouse_invert_slide_lr].value, PlayerCfg.MouseSens[2]);
1353 
1354 	//----------- Read bank_time -----------------
1355 	if ( Controls.state.bank_on )
1356 	{
1357 		// From keyboard...
1358 		adjust_ramped_keyboard_field(plus, key_heading_left, Controls.bank_time, PlayerCfg.KeyboardSens[4], speed_factor);
1359 		adjust_ramped_keyboard_field(minus, key_heading_right, Controls.bank_time, PlayerCfg.KeyboardSens[4], speed_factor);
1360 		// From joystick...
1361 #if defined(dxx_kconfig_ui_kc_joystick_turn) && defined(dxx_kconfig_ui_kc_joystick_bank)
1362 		adjust_axis_field(Controls.bank_time, Controls.joy_axis, kcm_joystick[dxx_kconfig_ui_kc_joystick_turn].value, kcm_joystick[dxx_kconfig_ui_kc_joystick_invert_bank].value, PlayerCfg.JoystickSens[4]);
1363 #endif
1364 		// From mouse...
1365 		adjust_axis_field(Controls.bank_time, Controls.mouse_axis, kcm_mouse[dxx_kconfig_ui_kc_mouse_turn].value, !kcm_mouse[dxx_kconfig_ui_kc_mouse_invert_bank].value, PlayerCfg.MouseSens[4]);
1366 	}
1367 	// From keyboard...
1368 	adjust_ramped_keyboard_field(plus, key_bank_left, Controls.bank_time, PlayerCfg.KeyboardSens[4], speed_factor);
1369 	adjust_ramped_keyboard_field(minus, key_bank_right, Controls.bank_time, PlayerCfg.KeyboardSens[4], speed_factor);
1370 	// From buttons...
1371 	adjust_button_time(Controls.bank_time, Controls.state.btn_bank_left, Controls.state.btn_bank_right, speed_factor);
1372 	// From joystick...
1373 #ifdef dxx_kconfig_ui_kc_joystick_bank
1374 	adjust_axis_field(Controls.bank_time, Controls.joy_axis, kcm_joystick[dxx_kconfig_ui_kc_joystick_bank].value, kcm_joystick[dxx_kconfig_ui_kc_joystick_invert_bank].value, PlayerCfg.JoystickSens[4]);
1375 #endif
1376 	// From mouse...
1377 	adjust_axis_field(Controls.bank_time, Controls.mouse_axis, kcm_mouse[dxx_kconfig_ui_kc_mouse_bank].value, !kcm_mouse[dxx_kconfig_ui_kc_mouse_invert_bank].value, PlayerCfg.MouseSens[4]);
1378 
1379 	//----------- Read forward_thrust_time -------------
1380 	// From keyboard/buttons...
1381 	adjust_button_time(Controls.forward_thrust_time, Controls.state.accelerate, Controls.state.reverse, speed_factor);
1382 	// From joystick...
1383 #ifdef dxx_kconfig_ui_kc_joystick_throttle
1384 	adjust_axis_field(Controls.forward_thrust_time, Controls.joy_axis, kcm_joystick[dxx_kconfig_ui_kc_joystick_throttle].value, kcm_joystick[dxx_kconfig_ui_kc_joystick_invert_throttle].value, PlayerCfg.JoystickSens[5]);
1385 #endif
1386 	// From mouse...
1387 	adjust_axis_field(Controls.forward_thrust_time, Controls.mouse_axis, kcm_mouse[dxx_kconfig_ui_kc_mouse_throttle].value, kcm_mouse[dxx_kconfig_ui_kc_mouse_invert_throttle].value, PlayerCfg.MouseSens[5]);
1388 
1389 	//----------- Read cruise-control-type of throttle.
1390 	if ( Controls.state.cruise_off > 0 ) Controls.state.cruise_off = Cruise_speed = 0;
1391 	else
1392 	{
1393 		adjust_button_time(Cruise_speed, Controls.state.cruise_plus, Controls.state.cruise_minus, speed_factor * 80);
1394 		clamp_value(Cruise_speed, 0, i2f(100));
1395 		if (Controls.forward_thrust_time == 0)
1396 			Controls.forward_thrust_time = fixmul(Cruise_speed, frametime) / 100;
1397 	}
1398 
1399 	//----------- Clamp values between -FrameTime and FrameTime
1400 	clamp_kconfig_control_with_overrun(Controls.vertical_thrust_time, frametime, Controls.excess_vertical_thrust_time, frametime * PlayerCfg.MouseOverrun[3]);
1401 	clamp_kconfig_control_with_overrun(Controls.sideways_thrust_time, frametime, Controls.excess_sideways_thrust_time, frametime * PlayerCfg.MouseOverrun[2]);
1402 	clamp_kconfig_control_with_overrun(Controls.forward_thrust_time, frametime, Controls.excess_forward_thrust_time, frametime * PlayerCfg.MouseOverrun[5]);
1403 	if (!allow_uncapped_turning())
1404 	{
1405 		clamp_kconfig_control_with_overrun(Controls.pitch_time, frametime / 2, Controls.excess_pitch_time, frametime * PlayerCfg.MouseOverrun[1]);
1406 		clamp_kconfig_control_with_overrun(Controls.heading_time, frametime, Controls.excess_heading_time, frametime * PlayerCfg.MouseOverrun[0]);
1407 		clamp_kconfig_control_with_overrun(Controls.bank_time, frametime, Controls.excess_bank_time, frametime * PlayerCfg.MouseOverrun[4]);
1408 	}
1409 }
1410 
1411 }
1412 
reset_cruise(void)1413 void reset_cruise(void)
1414 {
1415 	Cruise_speed=0;
1416 }
1417 
1418 
kc_set_controls()1419 void kc_set_controls()
1420 {
1421 	for (auto &&[kcm, setting] : zip(kcm_keyboard, PlayerCfg.KeySettings.Keyboard))
1422 		kcm.oldvalue = kcm.value = setting;
1423 
1424 #if DXX_MAX_JOYSTICKS
1425 	for (auto &&[kcm, kc, setting] : zip(kcm_joystick, kc_joystick, PlayerCfg.KeySettings.Joystick))
1426 	{
1427 		uint8_t value = setting;
1428 		if (kc.type == BT_INVERT)
1429 		{
1430 			if (value != 1)
1431 				value = 0;
1432 			setting = value;
1433 		}
1434 		kcm.oldvalue = kcm.value = value;
1435 	}
1436 #endif
1437 
1438 	for (auto &&[kcm, kc, setting] : zip(kcm_mouse, kc_mouse, PlayerCfg.KeySettings.Mouse))
1439 	{
1440 		uint8_t value = setting;
1441 		if (kc.type == BT_INVERT)
1442 		{
1443 			if (value != 1)
1444 				value = 0;
1445 			setting = value;
1446 		}
1447 		kcm.oldvalue = kcm.value = value;
1448 	}
1449 
1450 	for (auto &&[kcm, setting] : zip(kcm_rebirth, PlayerCfg.KeySettingsRebirth))
1451 		kcm.oldvalue = kcm.value = setting;
1452 }
1453