DEADSOFTWARE

menu: draw menus without direct access to sutructures
[flatwaifu.git] / src / menu.c
1 /*
2 Copyright (C) Prikol Software 1996-1997
3 Copyright (C) Aleksey Volynskov 1996-1997
4 Copyright (C) <ARembo@gmail.com> 2011
6 This file is part of the Doom2D:Rembo project.
8 Doom2D:Rembo is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License version 2 as
10 published by the Free Software Foundation.
12 Doom2D:Rembo is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, see <http://www.gnu.org/licenses/> or
19 write to the Free Software Foundation, Inc.,
20 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
21 */
23 #include "glob.h"
24 #include "files.h"
25 #include "memory.h"
26 #include "error.h"
27 #include "sound.h"
28 #include "view.h"
29 #include "player.h"
30 #include "switch.h"
31 #include "menu.h"
32 #include "misc.h"
33 #include "render.h"
34 #include "config.h"
35 #include "game.h"
36 #include "player.h"
37 #include "sound.h"
38 #include "music.h"
39 #include "input.h"
40 #include "system.h"
42 #include <stdio.h>
43 #include <string.h>
44 #include <stdlib.h>
45 #include <assert.h>
47 #define PCOLORN 10
48 static byte pcolortab[PCOLORN] = {
49 0x18, 0x20, 0x40, 0x58, 0x60, 0x70, 0x80, 0xB0, 0xC0, 0xD0
50 };
51 static int p1color = 5;
52 static int p2color = 4;
54 byte _warp;
56 #define MAX_STACK 8
57 static struct {
58 int n;
59 const new_menu_t *m;
60 } stack[MAX_STACK];
61 static int stack_p = -1;
63 #define GM_MAX_INPUT 24
64 char ibuf[GM_MAX_INPUT];
65 byte input;
66 int icur;
67 int imax;
68 static byte cbuf[32];
69 short lastkey;
71 #define QSND_NUM 14
72 static int qsnd[QSND_NUM];
73 static snd_t *csnd1, *csnd2, *msnd1, *msnd2, *msnd3, *msnd4, *msnd5, *msnd6;
74 static snd_t *voc;
75 static int voc_ch;
77 static void GM_stop (void) {
78 if (voc != NULL) {
79 if (voc_ch) {
80 S_stop(voc_ch);
81 voc_ch = 0;
82 }
83 S_free(voc);
84 voc = NULL;
85 }
86 }
88 static void GM_say (const char nm[8]) {
89 snd_t *snd = S_load(nm);
90 if (snd) {
91 GM_stop();
92 voc = S_load(nm);
93 voc_ch = S_play(voc, 0, 255);
94 }
95 }
97 int GM_init_int0 (new_msg_t *msg, int i, int a, int b, int s) {
98 assert(msg != NULL);
99 msg->integer.i = i;
100 msg->integer.a = a;
101 msg->integer.b = b;
102 msg->integer.s = s;
103 return 1;
106 int GM_init_int (new_msg_t *msg, int i, int a, int b, int s) {
107 assert(msg != NULL);
108 assert(a <= b);
109 assert(s >= 0);
110 return GM_init_int0(msg, min(max(i, a), b), a, b, s);
113 int GM_init_str (new_msg_t *msg, char *str, int maxlen) {
114 assert(msg != NULL);
115 assert(str != NULL);
116 assert(maxlen >= 0);
117 msg->string.s = str;
118 msg->string.maxlen = maxlen;
119 return 1;
122 static int GM_newgame_handler (new_msg_t *msg, const new_menu_t *m, void *data) {
123 assert(msg != NULL);
124 intptr_t i = (intptr_t)data;
125 switch (msg->type) {
126 case GM_ENTER:
127 GM_say("_NEWGAME");
128 return 1;
129 case GM_SELECT:
130 _2pl = 0;
131 g_dm = 0;
132 switch (i) {
133 case 0: GM_say("_1PLAYER"); break;
134 case 1: GM_say("_2PLAYER"); break;
135 case 2: GM_say("_DM"); break;
136 // GM_say("_COOP");
138 switch (i) {
139 case 2: // DEATHMATCH
140 g_dm = 1;
141 case 1: // COOPERATIVE
142 _2pl = 1;
143 case 0: // SINGLEPLAYER
144 g_map = _warp ? _warp : 1;
145 PL_reset();
146 pl1.color = pcolortab[p1color];
147 pl2.color = pcolortab[p2color];
148 G_start();
149 GM_popall();
150 return 1;
152 break;
154 return 0;
157 static int GM_var_handler (new_msg_t *msg, const new_menu_t *m, void *data) {
158 assert(msg != NULL);
159 if (data == &snd_vol) {
160 switch (msg->type) {
161 case GM_GETINT: return GM_init_int(msg, snd_vol, 0, 128, 8);
162 case GM_SETINT: S_volume(msg->integer.i); return 1;
164 } else if (data == &mus_vol) {
165 switch (msg->type) {
166 case GM_GETINT: return GM_init_int(msg, mus_vol, 0, 128, 8);
167 case GM_SETINT: S_volumemusic(msg->integer.i); return 1;
169 } else if (data == g_music) {
170 switch (msg->type) {
171 case GM_GETSTR:
172 return GM_init_str(msg, g_music, 8);
173 case GM_SELECT:
174 F_freemus();
175 F_nextmus(g_music);
176 F_loadmus(g_music);
177 S_startmusic(music_time * 2); // ???
178 return 1;
181 return 0;
184 static int GM_load_handler (new_msg_t *msg, const new_menu_t *m, void *data) {
185 assert(msg != NULL);
186 intptr_t i = (intptr_t)data;
187 switch (msg->type) {
188 case GM_ENTER:
189 F_getsavnames();
190 return 1;
191 case GM_GETSTR:
192 return GM_init_str(msg, (char*)savname[i], 24);
193 case GM_SELECT:
194 if (savok[i]) {
195 load_game(i);
196 GM_popall();
197 return 1;
199 break;
201 return 0;
204 static int GM_save_handler (new_msg_t *msg, const new_menu_t *m, void *data) {
205 assert(msg != NULL);
206 intptr_t i = (intptr_t)data;
207 switch (msg->type) {
208 case GM_ENTER:
209 if (g_st == GS_GAME) {
210 F_getsavnames();
211 } else {
212 GM_pop();
214 return 1;
215 case GM_GETSTR:
216 F_getsavnames();
217 return GM_init_str(msg, (char*)savname[i], 24);
218 case GM_END:
219 if (g_st == GS_GAME) {
220 F_savegame(i, msg->string.s); // TODO check size
221 GM_popall();
222 return 1;
224 break;
226 return 0;
229 static int GM_options_handler (new_msg_t *msg, const new_menu_t *m, void *data) {
230 switch (msg->type) {
231 case GM_SELECT: GM_push(R_menu()); return 1;
233 return 0;
236 static int GM_exit_handler (new_msg_t *msg, const new_menu_t *m, void *data) {
237 switch (msg->type) {
238 case GM_ENTER:
239 GM_say(rand() & 1 ? "_EXIT1" : "_EXIT2");
240 return 1;
241 case GM_SELECT:
242 if (data != NULL) {
243 F_freemus();
244 GM_stop();
245 Z_sound(S_get(qsnd[myrand(QSND_NUM)]), 255);
246 S_wait();
247 ERR_quit();
248 } else {
249 GM_pop();
251 return 1;
253 return 0;
256 static const new_menu_t newgame_menu = {
257 GM_BIG, "New game", NULL, NULL,
259 { GM_BUTTON, "One player", (void*)0, &GM_newgame_handler, NULL },
260 { GM_BUTTON, "Two players", (void*)1, &GM_newgame_handler, NULL },
261 { GM_BUTTON, "Deathmatch", (void*)2, &GM_newgame_handler, NULL },
262 { 0, NULL, NULL, NULL, NULL } // end
264 }, loadgame_menu = {
265 GM_BIG, "Load game", NULL, &GM_load_handler,
267 { GM_TEXTFIELD_BUTTON, "", (void*)0, &GM_load_handler, NULL },
268 { GM_TEXTFIELD_BUTTON, "", (void*)1, &GM_load_handler, NULL },
269 { GM_TEXTFIELD_BUTTON, "", (void*)2, &GM_load_handler, NULL },
270 { GM_TEXTFIELD_BUTTON, "", (void*)3, &GM_load_handler, NULL },
271 { GM_TEXTFIELD_BUTTON, "", (void*)4, &GM_load_handler, NULL },
272 { GM_TEXTFIELD_BUTTON, "", (void*)5, &GM_load_handler, NULL },
273 { GM_TEXTFIELD_BUTTON, "", (void*)6, &GM_load_handler, NULL },
274 { 0, NULL, NULL, NULL, NULL } // end
276 }, savegame_menu = {
277 GM_BIG, "Save game", NULL, &GM_save_handler,
279 { GM_TEXTFIELD, "", (void*)0, &GM_save_handler, NULL },
280 { GM_TEXTFIELD, "", (void*)1, &GM_save_handler, NULL },
281 { GM_TEXTFIELD, "", (void*)2, &GM_save_handler, NULL },
282 { GM_TEXTFIELD, "", (void*)3, &GM_save_handler, NULL },
283 { GM_TEXTFIELD, "", (void*)4, &GM_save_handler, NULL },
284 { GM_TEXTFIELD, "", (void*)5, &GM_save_handler, NULL },
285 { GM_TEXTFIELD, "", (void*)6, &GM_save_handler, NULL },
286 { 0, NULL, NULL, NULL, NULL } // end
288 }, sound_menu = {
289 GM_BIG, "Sound", NULL, NULL,
291 { GM_SCROLLER, "Volume", &snd_vol, &GM_var_handler, NULL },
292 { 0, NULL, NULL, NULL, NULL } // end
294 }, music_menu = {
295 GM_BIG, "Music", NULL, NULL,
297 { GM_SCROLLER, "Volume", &mus_vol, &GM_var_handler, NULL },
298 { GM_BUTTON, "Music: ", g_music, &GM_var_handler, NULL },
299 { 0, NULL, NULL, NULL, NULL } // end
301 }, options_menu = {
302 GM_BIG, "Options", NULL, NULL,
304 { GM_BUTTON, "Video", NULL, &GM_options_handler, NULL },
305 { GM_BUTTON, "Sound", NULL, NULL, &sound_menu },
306 { GM_BUTTON, "Music", NULL, NULL, &music_menu },
307 { 0, NULL, NULL, NULL, NULL } // end
309 }, exit_menu = {
310 GM_SMALL, "You are sure?", NULL, &GM_exit_handler,
312 { GM_SMALL_BUTTON, "Yes", (void*)1, &GM_exit_handler, NULL },
313 { GM_SMALL_BUTTON, "No", (void*)0, &GM_exit_handler, NULL },
314 { 0, NULL, NULL, NULL, NULL } // end
316 }, main_menu = {
317 GM_BIG, "Menu", NULL, NULL,
319 { GM_BUTTON, "New game", NULL, NULL, &newgame_menu },
320 { GM_BUTTON, "Load game", NULL, NULL, &loadgame_menu },
321 { GM_BUTTON, "Save game", NULL, NULL, &savegame_menu },
322 { GM_BUTTON, "Options", NULL, NULL, &options_menu },
323 { GM_BUTTON, "Exit", NULL, NULL, &exit_menu },
324 { 0, NULL, NULL, NULL, NULL } // end
326 };
328 void GM_push (const new_menu_t *m) {
329 assert(m != NULL);
330 assert(stack_p >= -1);
331 assert(stack_p < MAX_STACK - 1);
332 new_msg_t msg;
333 stack_p += 1;
334 if (stack[stack_p].m != m) {
335 stack[stack_p].n = 0;
336 stack[stack_p].m = m;
338 msg.type = GM_ENTER;
339 GM_send_this(m, &msg);
342 void GM_pop (void) {
343 assert(stack_p >= 0);
344 new_msg_t msg;
345 stack_p -= 1;
346 msg.type = GM_LEAVE;
347 GM_send_this(stack[stack_p + 1].m, &msg);
350 void GM_popall (void) {
351 int i;
352 for (i = 0; i >= -1; i--) {
353 GM_pop();
357 const new_menu_t *GM_get (void) {
358 if (stack_p >= 0) {
359 return stack[stack_p].m;
360 } else {
361 return NULL;
365 int GM_geti (void) {
366 if (stack_p >= 0) {
367 return stack[stack_p].n;
368 } else {
369 return 0;
373 static void GM_normalize_message (new_msg_t *msg) {
374 switch (msg->type) {
375 case GM_SETINT:
376 msg->integer.i = min(max(msg->integer.i, msg->integer.a), msg->integer.b);
377 break;
378 case GM_SETSTR:
379 assert(msg->string.maxlen >= 0);
380 break;
384 static int count_menu_entries (const new_menu_t *m) {
385 assert(m != NULL);
386 int i = 0;
387 while (m->entries[i].type != 0) {
388 i += 1;
390 return i;
393 int GM_send_this (const new_menu_t *m, new_msg_t *msg) {
394 assert(m != NULL);
395 assert(msg != NULL);
396 int n;
397 switch (msg->type) {
398 case GM_QUERY:
399 n = count_menu_entries(m);
400 return GM_init_int0(msg, GM_geti(), n, n, m->type);
401 case GM_GETTITLE:
402 return GM_init_str(msg, m->title, strlen(m->title));
403 default:
404 if (m->handler != NULL) {
405 GM_normalize_message(msg);
406 return m->handler(msg, m, m->data);
409 return 0;
412 int GM_send (const new_menu_t *m, int i, new_msg_t *msg) {
413 assert(m != NULL);
414 assert(i >= 0);
415 assert(msg != NULL);
416 const new_var_t *v = &m->entries[i];
417 switch (msg->type) {
418 case GM_GETENTRY: return GM_init_int0(msg, v->type, 0, 0, 0);
419 case GM_GETCAPTION: return GM_init_str(msg, v->caption, strlen(v->caption));
420 default:
421 if (v->handler != NULL) {
422 GM_normalize_message(msg);
423 return v->handler(msg, m, v->data);
426 return 0;
429 void G_code (void) {
430 void *s;
431 s=csnd2;
432 if(memcmp(cbuf+32-5,"IDDQD",5)==0) {
433 PL_hit(&pl1,400,0,HIT_SOME);
434 if(_2pl) PL_hit(&pl2,400,0,HIT_SOME);
435 s=csnd1;
436 }else if(memcmp(cbuf+32-4,"TANK",4)==0) {
437 pl1.life=pl1.armor=200;pl1.drawst|=PL_DRAWARMOR|PL_DRAWLIFE;
438 if(_2pl) {pl2.life=pl2.armor=200;pl2.drawst|=PL_DRAWARMOR|PL_DRAWLIFE;}
439 }else if(memcmp(cbuf+32-8,"BULLFROG",8)==0) {
440 PL_JUMP=(PL_JUMP==10)?20:10;
441 }else if(memcmp(cbuf+32-8,"FORMULA1",8)==0) {
442 PL_RUN=(PL_RUN==8)?24:8;
443 }else if(memcmp(cbuf+32-5,"RAMBO",5)==0) {
444 pl1.ammo=pl1.shel=pl1.rock=pl1.cell=pl1.fuel=30000;
445 pl1.wpns=0x7FF;pl1.drawst|=PL_DRAWWPN|PL_DRAWKEYS;
446 pl1.keys=0x70;
447 if(_2pl) {
448 pl2.ammo=pl2.shel=pl2.rock=pl2.cell=pl1.fuel=30000;
449 pl2.wpns=0x7FF;pl2.drawst|=PL_DRAWWPN|PL_DRAWKEYS;
450 pl2.keys=0x70;
452 }else if(memcmp(cbuf+32-5,"UJHTW",5)==0) {
453 p_immortal=!p_immortal;
454 }else if(memcmp(cbuf+32-9,",TKSQJHTK",9)==0) {
455 p_fly=!p_fly;
456 }else if(memcmp(cbuf+32-6,"CBVCBV",6)==0) {
457 SW_cheat_open();
458 }else if(memcmp(cbuf+32-7,"GOODBYE",7)==0) {
459 g_exit=1;
460 }else if(memcmp(cbuf+32-9,"GJITKYF",7)==0) {
461 if(cbuf[30]>='0' && cbuf[30]<='9' && cbuf[31]>='0' && cbuf[31]<='9') {
462 g_map=(cbuf[30]=='0')?0:(cbuf[30]-'0')*10;
463 g_map+=(cbuf[31]=='0')?0:(cbuf[31]-'0');
464 G_start();
466 }else return;
467 memset(cbuf,0,32);
468 Z_sound(s,128);
471 static int strnlen (const char *s, int len) {
472 int i = 0;
473 while (i < len && s[i] != 0) {
474 i++;
476 return i;
479 static int state_for_anykey (int x) {
480 return x == GS_TITLE || x == GS_ENDSCR;
483 int GM_act (void) {
484 int n, cur;
485 new_msg_t msg;
486 const new_var_t *v;
487 const new_menu_t *m = GM_get ();
488 if (m == NULL) {
489 if (lastkey == KEY_ESCAPE || (state_for_anykey(g_st) && lastkey != KEY_UNKNOWN)) {
490 GM_push(&main_menu);
491 Z_sound(msnd3, 128);
493 } else {
494 n = count_menu_entries(m);
495 cur = stack[stack_p].n;
496 v = &m->entries[cur];
497 switch (lastkey) {
498 case KEY_ESCAPE:
499 if (v->type == GM_TEXTFIELD && input) {
500 input = 0;
501 Y_disable_text_input();
502 msg.type = GM_CANCEL;
503 GM_send(m, cur, &msg);
504 } else {
505 GM_pop();
506 Z_sound(msnd4, 128);
508 break;
509 case KEY_UP:
510 stack[stack_p].n = stack[stack_p].n - 1 < 0 ? n - 1 : stack[stack_p].n - 1;
511 Z_sound(msnd1, 128);
512 break;
513 case KEY_DOWN:
514 stack[stack_p].n = stack[stack_p].n + 1 >= n ? 0 : stack[stack_p].n + 1;
515 Z_sound(msnd1, 128);
516 break;
517 case KEY_LEFT:
518 case KEY_RIGHT:
519 if (v->type == GM_SCROLLER) {
520 msg.integer.type = GM_GETINT;
521 if (GM_send(m, cur, &msg)) {
522 msg.integer.type = GM_SETINT;
523 msg.integer.i += lastkey == KEY_LEFT ? -msg.integer.s : msg.integer.s;
524 msg.integer.i = min(max(msg.integer.i, msg.integer.a), msg.integer.b);
525 if (GM_send(m, cur, &msg)) {
526 Z_sound(lastkey == KEY_LEFT ? msnd5 : msnd6, 255);
529 } else if (v->type == GM_TEXTFIELD && input) {
530 //icur += lastkey == KEY_LEFT ? -1 : +1;
531 //icur = min(max(icur, 0), strnlen(ibuf, imax));
533 break;
534 case KEY_BACKSPACE:
535 if (v->type == GM_TEXTFIELD && input) {
536 if (icur > 0) {
537 // FIXIT buffers in strncpy must not overlap
538 strncpy(&ibuf[icur - 1], &ibuf[icur], imax - icur);
539 ibuf[imax - 1] = 0;
540 icur -= 1;
543 break;
544 case KEY_RETURN:
545 if (v->submenu != NULL) {
546 GM_push(v->submenu);
547 Z_sound(msnd2, 128);
548 } else if (v->type == GM_TEXTFIELD) {
549 if (input) {
550 input = 0;
551 Y_disable_text_input();
552 msg.type = GM_END;
553 msg.string.s = ibuf;
554 msg.string.maxlen = imax;
555 GM_send(m, cur, &msg);
556 } else {
557 msg.type = GM_GETSTR;
558 if (GM_send(m, cur, &msg)) {
559 imax = min(msg.string.maxlen, GM_MAX_INPUT);
560 strncpy(ibuf, msg.string.s, imax);
561 icur = strnlen(ibuf, imax);
563 input = 1;
564 Y_enable_text_input();
565 msg.type = GM_BEGIN;
566 GM_send(m, cur, &msg);
568 } else {
569 msg.type = GM_SELECT;
570 GM_send(m, cur, &msg);
572 break;
575 lastkey = KEY_UNKNOWN;
576 return m != NULL;
579 void GM_input (int ch) {
580 if (ch != 0 && input) {
581 if (icur < imax) {
582 ibuf[icur] = ch;
583 icur += 1;
584 if (icur < imax) {
585 ibuf[icur] = 0;
591 void GM_key (int key, int down) {
592 int i;
593 if (down) {
594 lastkey = key;
595 if (!_2pl || cheat) {
596 for (i = 0; i < 31; i++) {
597 cbuf[i] = cbuf[i + 1];
599 if (key >= KEY_0 && key <= KEY_9) {
600 cbuf[31] = key - KEY_0 + '0';
601 } else if (key >= KEY_A && key <= KEY_Z) {
602 cbuf[31] = key - KEY_A + 'A';
603 } else {
604 cbuf[31] = 0;
610 void GM_init (void) {
611 int i;
612 char s[8];
613 static const char nm[QSND_NUM][6] = {
614 "CYBSIT", "KNTDTH", "MNPAIN", "PEPAIN", "SLOP", "MANSIT", "BOSPN", "VILACT",
615 "PLFALL", "BGACT", "BGDTH2", "POPAIN", "SGTATK", "VILDTH"
616 };
617 s[0] = 'D';
618 s[1] = 'S';
619 for (i = 0; i < QSND_NUM; ++i) {
620 memcpy(s + 2, nm[i], 6);
621 qsnd[i] = F_getresid(s);
623 csnd1 = Z_getsnd("HAHA1");
624 csnd2 = Z_getsnd("RADIO");
625 msnd1 = Z_getsnd("PSTOP");
626 msnd2 = Z_getsnd("PISTOL");
627 msnd3 = Z_getsnd("SWTCHN");
628 msnd4 = Z_getsnd("SWTCHX");
629 msnd5 = Z_getsnd("SUDI");
630 msnd6 = Z_getsnd("TUDI");
631 F_loadmus("MENU");
632 S_startmusic(0);