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