1 /**
2  * @file gmenu.cpp
3  *
4  * Implementation of the in-game navigation and interaction.
5  */
6 #include "all.h"
7 
8 #include "../SourceX/controls/axis_direction.h"
9 #include "../SourceX/controls/controller_motion.h"
10 
11 DEVILUTION_BEGIN_NAMESPACE
12 
13 BYTE *optbar_cel;
14 BOOLEAN mouseNavigation;
15 BYTE *PentSpin_cel;
16 TMenuItem *sgpCurrItem;
17 BYTE *BigTGold_cel;
18 int LogoAnim_tick;
19 BYTE LogoAnim_frame;
20 int PentSpin_tick;
21 void (*gmenu_current_option)(TMenuItem *);
22 TMenuItem *sgpCurrentMenu;
23 BYTE *option_cel;
24 BYTE *sgpLogo;
25 int sgCurrentMenuIdx;
26 
27 /** Maps from font index to bigtgold.cel frame number. */
28 const BYTE lfontframe[] = {
29 	0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
30 	0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
31 	0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
32 	0, 0, 0, 37, 49, 38, 0, 39, 40, 47,
33 	42, 43, 41, 45, 52, 44, 53, 55, 36, 27,
34 	28, 29, 30, 31, 32, 33, 34, 35, 51, 50,
35 	0, 46, 0, 54, 0, 1, 2, 3, 4, 5,
36 	6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
37 	16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
38 	26, 42, 0, 43, 0, 0, 0, 1, 2, 3,
39 	4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
40 	14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
41 	24, 25, 26, 20, 0, 21, 0, 0
42 };
43 
44 /** Maps from bigtgold.cel frame number to character width. */
45 const BYTE lfontkern[] = {
46 	18, 33, 21, 26, 28, 19, 19, 26, 25, 11,
47 	12, 25, 19, 34, 28, 32, 20, 32, 28, 20,
48 	28, 36, 35, 46, 33, 33, 24, 11, 23, 22,
49 	22, 21, 22, 21, 21, 21, 32, 10, 20, 36,
50 	31, 17, 13, 12, 13, 18, 16, 11, 20, 21,
51 	11, 10, 12, 11, 21, 23
52 };
53 
gmenu_print_text(CelOutputBuffer out,int x,int y,const char * pszStr)54 static void gmenu_print_text(CelOutputBuffer out, int x, int y, const char *pszStr)
55 {
56 	BYTE c;
57 
58 	while (*pszStr) {
59 		c = gbFontTransTbl[(BYTE)*pszStr++];
60 		c = lfontframe[c];
61 		if (c != 0)
62 			CelDrawLightTo(out, x, y, BigTGold_cel, c, 46, NULL);
63 		x += lfontkern[c] + 2;
64 	}
65 }
66 
gmenu_draw_pause(CelOutputBuffer out)67 void gmenu_draw_pause(CelOutputBuffer out)
68 {
69 	if (currlevel != 0)
70 		RedBack(out);
71 	if (!sgpCurrentMenu) {
72 		light_table_index = 0;
73 		gmenu_print_text(out, 252 + PANEL_LEFT, 176, "Pause");
74 	}
75 }
76 
FreeGMenu()77 void FreeGMenu()
78 {
79 	MemFreeDbg(sgpLogo);
80 	MemFreeDbg(BigTGold_cel);
81 	MemFreeDbg(PentSpin_cel);
82 	MemFreeDbg(option_cel);
83 	MemFreeDbg(optbar_cel);
84 }
85 
gmenu_init_menu()86 void gmenu_init_menu()
87 {
88 	LogoAnim_frame = 1;
89 	sgpCurrentMenu = NULL;
90 	sgpCurrItem = NULL;
91 	gmenu_current_option = NULL;
92 	sgCurrentMenuIdx = 0;
93 	mouseNavigation = FALSE;
94 	if (gbIsHellfire)
95 		sgpLogo = LoadFileInMem("Data\\hf_logo3.CEL", NULL);
96 	else
97 		sgpLogo = LoadFileInMem("Data\\Diabsmal.CEL", NULL);
98 	BigTGold_cel = LoadFileInMem("Data\\BigTGold.CEL", NULL);
99 	PentSpin_cel = LoadFileInMem("Data\\PentSpin.CEL", NULL);
100 	option_cel = LoadFileInMem("Data\\option.CEL", NULL);
101 	optbar_cel = LoadFileInMem("Data\\optbar.CEL", NULL);
102 }
103 
gmenu_is_active()104 BOOL gmenu_is_active()
105 {
106 	return sgpCurrentMenu != NULL;
107 }
108 
gmenu_up_down(BOOL isDown)109 static void gmenu_up_down(BOOL isDown)
110 {
111 	int i;
112 
113 	if (!sgpCurrItem) {
114 		return;
115 	}
116 	mouseNavigation = FALSE;
117 	i = sgCurrentMenuIdx;
118 	if (sgCurrentMenuIdx) {
119 		while (i) {
120 			i--;
121 			if (isDown) {
122 				sgpCurrItem++;
123 				if (!sgpCurrItem->fnMenu)
124 					sgpCurrItem = &sgpCurrentMenu[0];
125 			} else {
126 				if (sgpCurrItem == sgpCurrentMenu)
127 					sgpCurrItem = &sgpCurrentMenu[sgCurrentMenuIdx];
128 				sgpCurrItem--;
129 			}
130 			if ((sgpCurrItem->dwFlags & GMENU_ENABLED) != 0) {
131 				if (i)
132 					PlaySFX(IS_TITLEMOV);
133 				return;
134 			}
135 		}
136 	}
137 }
138 
gmenu_left_right(BOOL isRight)139 static void gmenu_left_right(BOOL isRight)
140 {
141 	int step, steps;
142 
143 	if (!(sgpCurrItem->dwFlags & GMENU_SLIDER))
144 		return;
145 
146 	step = sgpCurrItem->dwFlags & 0xFFF;
147 	steps = (int)(sgpCurrItem->dwFlags & 0xFFF000) >> 12;
148 	if (isRight) {
149 		if (step == steps)
150 			return;
151 		step++;
152 	} else {
153 		if (step == 0)
154 			return;
155 		step--;
156 	}
157 	sgpCurrItem->dwFlags &= 0xFFFFF000;
158 	sgpCurrItem->dwFlags |= step;
159 	sgpCurrItem->fnMenu(FALSE);
160 }
161 
gmenu_set_items(TMenuItem * pItem,void (* gmFunc)(TMenuItem *))162 void gmenu_set_items(TMenuItem *pItem, void (*gmFunc)(TMenuItem *))
163 {
164 	int i;
165 
166 	PauseMode = 0;
167 	mouseNavigation = FALSE;
168 	sgpCurrentMenu = pItem;
169 	gmenu_current_option = gmFunc;
170 	if (gmFunc) {
171 		gmenu_current_option(sgpCurrentMenu);
172 		pItem = sgpCurrentMenu;
173 	}
174 	sgCurrentMenuIdx = 0;
175 	if (sgpCurrentMenu) {
176 		for (i = 0; sgpCurrentMenu[i].fnMenu; i++) {
177 			sgCurrentMenuIdx++;
178 		}
179 	}
180 	// BUGFIX: OOB access when sgCurrentMenuIdx is 0; should be set to NULL instead. (fixed)
181 	sgpCurrItem = sgCurrentMenuIdx > 0 ? &sgpCurrentMenu[sgCurrentMenuIdx - 1] : NULL;
182 	gmenu_up_down(TRUE);
183 }
184 
gmenu_clear_buffer(CelOutputBuffer out,int x,int y,int width,int height)185 static void gmenu_clear_buffer(CelOutputBuffer out, int x, int y, int width, int height)
186 {
187 	BYTE *i = out.at(x, y);
188 	while (height--) {
189 		memset(i, 205, width);
190 		i -= out.pitch();
191 	}
192 }
193 
gmenu_get_lfont(TMenuItem * pItem)194 static int gmenu_get_lfont(TMenuItem *pItem)
195 {
196 	const char *text;
197 	int i;
198 	BYTE c;
199 
200 	if (pItem->dwFlags & GMENU_SLIDER)
201 		return 490;
202 	text = pItem->pszStr;
203 	i = 0;
204 	while (*text) {
205 		c = gbFontTransTbl[(BYTE)*text++];
206 		i += lfontkern[lfontframe[c]] + 2;
207 	}
208 	return i - 2;
209 }
210 
gmenu_draw_menu_item(CelOutputBuffer out,TMenuItem * pItem,int y)211 static void gmenu_draw_menu_item(CelOutputBuffer out, TMenuItem *pItem, int y)
212 {
213 	DWORD w, x, nSteps, step, pos;
214 	w = gmenu_get_lfont(pItem);
215 	if (pItem->dwFlags & GMENU_SLIDER) {
216 		x = 16 + w / 2;
217 		CelDrawTo(out, x + PANEL_LEFT, y - 10, optbar_cel, 1, 287);
218 		step = pItem->dwFlags & 0xFFF;
219 		nSteps = (pItem->dwFlags & 0xFFF000) >> 12;
220 		if (nSteps < 2)
221 			nSteps = 2;
222 		pos = step * 256 / nSteps;
223 		gmenu_clear_buffer(out, x + 2 + PANEL_LEFT, y - 12, pos + 13, 28);
224 		CelDrawTo(out, x + 2 + pos + PANEL_LEFT, y - 12, option_cel, 1, 27);
225 	}
226 	x = gnScreenWidth / 2 - w / 2;
227 	light_table_index = (pItem->dwFlags & GMENU_ENABLED) ? 0 : 15;
228 	gmenu_print_text(out, x, y, pItem->pszStr);
229 	if (pItem == sgpCurrItem) {
230 		CelDrawTo(out, x - 54, y + 1, PentSpin_cel, PentSpn2Spin(), 48);
231 		CelDrawTo(out, x + 4 + w, y + 1, PentSpin_cel, PentSpn2Spin(), 48);
232 	}
233 }
234 
GameMenuMove()235 static void GameMenuMove()
236 {
237 	static AxisDirectionRepeater repeater;
238 	const AxisDirection move_dir = repeater.Get(GetLeftStickOrDpadDirection());
239 	if (move_dir.x != AxisDirectionX_NONE)
240 		gmenu_left_right(move_dir.x == AxisDirectionX_RIGHT);
241 	if (move_dir.y != AxisDirectionY_NONE)
242 		gmenu_up_down(move_dir.y == AxisDirectionY_DOWN);
243 }
244 
gmenu_draw(CelOutputBuffer out)245 void gmenu_draw(CelOutputBuffer out)
246 {
247 	int y;
248 	TMenuItem *i;
249 	DWORD ticks;
250 
251 	if (sgpCurrentMenu) {
252 		GameMenuMove();
253 		if (gmenu_current_option)
254 			gmenu_current_option(sgpCurrentMenu);
255 		if (gbIsHellfire) {
256 			ticks = SDL_GetTicks();
257 			if ((int)(ticks - LogoAnim_tick) > 25) {
258 				LogoAnim_frame++;
259 				if (LogoAnim_frame > 16)
260 					LogoAnim_frame = 1;
261 				LogoAnim_tick = ticks;
262 			}
263 			CelDrawTo(out, (gnScreenWidth - 430) / 2, 102 + UI_OFFSET_Y, sgpLogo, LogoAnim_frame, 430);
264 		} else {
265 			CelDrawTo(out, (gnScreenWidth - 296) / 2, 102 + UI_OFFSET_Y, sgpLogo, 1, 296);
266 		}
267 		y = 160 + UI_OFFSET_Y;
268 		i = sgpCurrentMenu;
269 		if (sgpCurrentMenu->fnMenu) {
270 			while (i->fnMenu) {
271 				gmenu_draw_menu_item(out, i, y);
272 				i++;
273 				y += 45;
274 			}
275 		}
276 	}
277 }
278 
gmenu_presskeys(int vkey)279 BOOL gmenu_presskeys(int vkey)
280 {
281 	if (!sgpCurrentMenu)
282 		return FALSE;
283 	switch (vkey) {
284 	case DVL_VK_RETURN:
285 		if ((sgpCurrItem->dwFlags & GMENU_ENABLED) != 0) {
286 			PlaySFX(IS_TITLEMOV);
287 			sgpCurrItem->fnMenu(TRUE);
288 		}
289 		break;
290 	case DVL_VK_ESCAPE:
291 		PlaySFX(IS_TITLEMOV);
292 		gmenu_set_items(NULL, NULL);
293 		break;
294 	case DVL_VK_SPACE:
295 		return FALSE;
296 	case DVL_VK_LEFT:
297 		gmenu_left_right(FALSE);
298 		break;
299 	case DVL_VK_RIGHT:
300 		gmenu_left_right(TRUE);
301 		break;
302 	case DVL_VK_UP:
303 		gmenu_up_down(FALSE);
304 		break;
305 	case DVL_VK_DOWN:
306 		gmenu_up_down(TRUE);
307 		break;
308 	}
309 	return TRUE;
310 }
311 
gmenu_get_mouse_slider(int * plOffset)312 static BOOLEAN gmenu_get_mouse_slider(int *plOffset)
313 {
314 	*plOffset = 282;
315 	if (MouseX < 282 + PANEL_LEFT) {
316 		*plOffset = 0;
317 		return FALSE;
318 	}
319 	if (MouseX > 538 + PANEL_LEFT) {
320 		*plOffset = 256;
321 		return FALSE;
322 	}
323 	*plOffset = MouseX - 282 - PANEL_LEFT;
324 	return TRUE;
325 }
326 
gmenu_on_mouse_move()327 BOOL gmenu_on_mouse_move()
328 {
329 	int step, nSteps;
330 
331 	if (!mouseNavigation)
332 		return FALSE;
333 	gmenu_get_mouse_slider(&step);
334 	nSteps = (int)(sgpCurrItem->dwFlags & 0xFFF000) >> 12;
335 	step *= nSteps;
336 	step /= 256;
337 
338 	sgpCurrItem->dwFlags &= 0xFFFFF000;
339 	sgpCurrItem->dwFlags |= step;
340 	sgpCurrItem->fnMenu(FALSE);
341 	return TRUE;
342 }
343 
gmenu_left_mouse(BOOL isDown)344 BOOL gmenu_left_mouse(BOOL isDown)
345 {
346 	TMenuItem *pItem;
347 	int i, w, dummy;
348 
349 	if (!isDown) {
350 		if (mouseNavigation) {
351 			mouseNavigation = FALSE;
352 			return TRUE;
353 		} else {
354 			return FALSE;
355 		}
356 	}
357 
358 	if (!sgpCurrentMenu) {
359 		return FALSE;
360 	}
361 	if (MouseY >= PANEL_TOP) {
362 		return FALSE;
363 	}
364 	if (MouseY - (117 + UI_OFFSET_Y) < 0) {
365 		return TRUE;
366 	}
367 	i = (MouseY - (117 + UI_OFFSET_Y)) / 45;
368 	if (i >= sgCurrentMenuIdx) {
369 		return TRUE;
370 	}
371 	pItem = &sgpCurrentMenu[i];
372 	if (!(sgpCurrentMenu[i].dwFlags & GMENU_ENABLED)) {
373 		return TRUE;
374 	}
375 	w = gmenu_get_lfont(pItem);
376 	if (MouseX < gnScreenWidth / 2 - w / 2) {
377 		return TRUE;
378 	}
379 	if (MouseX > gnScreenWidth / 2 + w / 2) {
380 		return TRUE;
381 	}
382 	sgpCurrItem = pItem;
383 	PlaySFX(IS_TITLEMOV);
384 	if (pItem->dwFlags & GMENU_SLIDER) {
385 		mouseNavigation = gmenu_get_mouse_slider(&dummy);
386 		gmenu_on_mouse_move();
387 	} else {
388 		sgpCurrItem->fnMenu(TRUE);
389 	}
390 	return TRUE;
391 }
392 
gmenu_enable(TMenuItem * pMenuItem,BOOL enable)393 void gmenu_enable(TMenuItem *pMenuItem, BOOL enable)
394 {
395 	if (enable)
396 		pMenuItem->dwFlags |= GMENU_ENABLED;
397 	else
398 		pMenuItem->dwFlags &= ~GMENU_ENABLED;
399 }
400 
401 /**
402  * @brief Set the TMenuItem slider position based on the given value
403  */
gmenu_slider_set(TMenuItem * pItem,int min,int max,int value)404 void gmenu_slider_set(TMenuItem *pItem, int min, int max, int value)
405 {
406 	int nSteps;
407 
408 	assert(pItem);
409 	nSteps = (int)(pItem->dwFlags & 0xFFF000) >> 12;
410 	if (nSteps < 2)
411 		nSteps = 2;
412 	pItem->dwFlags &= 0xFFFFF000;
413 	pItem->dwFlags |= ((max - min - 1) / 2 + (value - min) * nSteps) / (max - min);
414 }
415 
416 /**
417  * @brief Get the current value for the slider
418  */
gmenu_slider_get(TMenuItem * pItem,int min,int max)419 int gmenu_slider_get(TMenuItem *pItem, int min, int max)
420 {
421 	int nSteps, step;
422 
423 	step = pItem->dwFlags & 0xFFF;
424 	nSteps = (int)(pItem->dwFlags & 0xFFF000) >> 12;
425 	if (nSteps < 2)
426 		nSteps = 2;
427 	return min + (step * (max - min) + (nSteps - 1) / 2) / nSteps;
428 }
429 
430 /**
431  * @brief Set the number of steps for the slider
432  */
gmenu_slider_steps(TMenuItem * pItem,int steps)433 void gmenu_slider_steps(TMenuItem *pItem, int steps)
434 {
435 	pItem->dwFlags &= 0xFF000FFF;
436 	pItem->dwFlags |= (steps << 12) & 0xFFF000;
437 }
438 
439 DEVILUTION_END_NAMESPACE
440