DEADSOFTWARE

portability: avoid errors on some compilers
[flatwaifu.git] / src / menu.c
1 /* Copyright (C) 2020 SovietPony
2 *
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, version 3 of the License ONLY.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License
13 * along with this program. If not, see <http://www.gnu.org/licenses/>.
14 */
16 #include "glob.h"
17 #include "files.h"
18 #include "memory.h"
19 #include "error.h"
20 #include "sound.h"
21 #include "view.h"
22 #include "player.h"
23 #include "switch.h"
24 #include "menu.h"
25 #include "misc.h"
26 #include "render.h"
27 #include "config.h"
28 #include "game.h"
29 #include "player.h"
30 #include "sound.h"
31 #include "music.h"
32 #include "input.h"
33 #include "system.h"
35 #include "save.h"
37 #include <stdio.h>
38 #include <string.h>
39 #include <stdlib.h>
40 #include <assert.h>
42 #define PCOLORN 10
43 static byte pcolortab[PCOLORN] = {
44 0x18, 0x20, 0x40, 0x58, 0x60, 0x70, 0x80, 0xB0, 0xC0, 0xD0
45 };
46 static int p1color = 5;
47 static int p2color = 4;
49 byte _warp;
51 #define MAX_STACK 8
52 static struct {
53 const menu_t *m;
54 } stack[MAX_STACK];
55 static int stack_p = -1;
57 #define GM_MAX_INPUT 24
58 char ibuf[GM_MAX_INPUT];
59 byte input;
60 int icur;
61 int imax;
62 static byte cbuf[32];
63 short lastkey;
65 #define QSND_NUM 14
66 static int qsnd[QSND_NUM];
67 static snd_t *csnd1, *csnd2, *msnd1, *msnd2, *msnd3, *msnd4, *msnd5, *msnd6;
68 static snd_t *voc;
69 static int voc_ch;
71 static void GM_stop (void) {
72 if (voc != NULL) {
73 if (voc_ch) {
74 S_stop(voc_ch);
75 voc_ch = 0;
76 }
77 S_free(voc);
78 voc = NULL;
79 }
80 }
82 static int GM_say (const char nm[8]) {
83 snd_t *snd = S_load(nm);
84 if (snd) {
85 GM_stop();
86 voc = S_load(nm);
87 voc_ch = S_play(voc, 0, 255);
88 }
89 return 1;
90 }
92 int GM_init_int0 (menu_msg_t *msg, int i, int a, int b, int s) {
93 assert(msg != NULL);
94 msg->integer.i = i;
95 msg->integer.a = a;
96 msg->integer.b = b;
97 msg->integer.s = s;
98 return 1;
99 }
101 int GM_init_int (menu_msg_t *msg, int i, int a, int b, int s) {
102 assert(msg != NULL);
103 assert(a <= b);
104 assert(s >= 0);
105 return GM_init_int0(msg, min(max(i, a), b), a, b, s);
108 int GM_init_str (menu_msg_t *msg, char *str, int maxlen) {
109 assert(msg != NULL);
110 assert(str != NULL);
111 assert(maxlen >= 0);
112 msg->string.s = str;
113 msg->string.maxlen = maxlen;
114 return 1;
117 int basic_menu_handler (menu_msg_t *msg, byte type, char *title, char *say, int n, int *cur) {
118 assert(msg != NULL);
119 assert(type == GM_BIG || type == GM_SMALL);
120 assert(title != NULL);
121 assert(n >= 0);
122 assert(cur != NULL);
123 switch (msg->type) {
124 case GM_QUERY: return GM_init_int0(msg, *cur, n, n, type);
125 case GM_GETTITLE: return GM_init_str(msg, title, strlen(title));
126 case GM_ENTER: return say ? GM_say(say) : 1;
127 case GM_UP: *cur = GM_CYCLE(*cur - 1, 0, n - 1); return 1;
128 case GM_DOWN: *cur = GM_CYCLE(*cur + 1, 0, n - 1); return 1;
130 return 0;
133 int simple_menu_handler (menu_msg_t *msg, int i, int n, const simple_menu_t *m, int *cur) {
134 assert(msg != NULL);
135 assert(n >= 0);
136 assert(i >= 0 && i < n);
137 assert(m != NULL);
138 assert(cur != NULL);
139 switch (msg->type) {
140 case GM_GETENTRY: return GM_init_int0(msg, m->type == GM_SMALL ? GM_SMALL_BUTTON : GM_BUTTON, 0, 0, 0);
141 case GM_GETCAPTION: return GM_init_str(msg, m->entries[i].caption, strlen(m->entries[i].caption));
142 case GM_SELECT: return m->entries[i].submenu ? GM_push(m->entries[i].submenu) : 1;
144 return basic_menu_handler(msg, m->type, m->title, m->say, n, cur);
147 static int start_game (int twoplayers, int dm, int level) {
148 _2pl = twoplayers;
149 g_dm = dm;
150 g_map = level ? level : 1;
151 PL_reset();
152 pl1.color = pcolortab[p1color];
153 pl2.color = pcolortab[p2color];
154 G_start();
155 return GM_popall();
158 static int new_game_menu_handler (menu_msg_t *msg, const menu_t *m, int i) {
159 static int cur;
160 enum { ONEPLAYER, TWOPLAYERS, DEATHMATCH, NG__NUM__ };
161 static const simple_menu_t sm = {
162 GM_BIG, "New Game", "_NEWGAME",
164 { "One Player", NULL },
165 { "Two Players", NULL },
166 { "Deathmatch", NULL },
168 };
169 if (msg->type == GM_SELECT) {
170 switch (i) {
171 case ONEPLAYER: GM_say("_1PLAYER"); return start_game(0, 0, _warp);
172 case TWOPLAYERS: GM_say("_2PLAYER"); return start_game(1, 0, _warp);
173 case DEATHMATCH: GM_say("_DM"); return start_game(1, 1, _warp);
174 // GM_say("_COOP");
177 return simple_menu_handler(msg, i, NG__NUM__, &sm, &cur);
180 static int load_game_menu_handler (menu_msg_t *msg, const menu_t *m, int i) {
181 static int cur;
182 const int max_slots = 7;
183 assert(i >= 0 && i < max_slots);
184 switch (msg->type) {
185 case GM_ENTER: F_getsavnames(); break;
186 case GM_GETENTRY: return GM_init_int0(msg, GM_TEXTFIELD_BUTTON, 0, 0, 0);
187 case GM_GETSTR: return GM_init_str(msg, (char*)savname[i], 24);
188 case GM_SELECT:
189 if (savok[i]) {
190 load_game(i);
191 GM_popall();
193 return 1;
195 return basic_menu_handler(msg, GM_BIG, "Load game", "_OLDGAME", max_slots, &cur);
198 static int save_game_menu_handler (menu_msg_t *msg, const menu_t *m, int i) {
199 static int cur;
200 const int max_slots = 7;
201 assert(i >= 0 && i < max_slots);
202 switch (msg->type) {
203 case GM_ENTER:
204 if (g_st == GS_GAME) {
205 F_getsavnames();
206 break;
207 } else {
208 return GM_pop();
210 case GM_GETENTRY: return GM_init_int0(msg, GM_TEXTFIELD, 0, 0, 0);
211 case GM_GETSTR: return GM_init_str(msg, (char*)savname[i], 24);
212 case GM_END:
213 if (g_st == GS_GAME) {
214 assert(msg->string.maxlen >= 24);
215 F_savegame(i, msg->string.s);
217 return GM_popall();
219 return basic_menu_handler(msg, GM_BIG, "Save game", "_SAVGAME", max_slots, &cur);
222 typedef struct controls_menu_t {
223 menu_t base;
224 int *pl_keys;
225 } controls_menu_t;
227 static int controls_menu_handler (menu_msg_t *msg, const menu_t *m, int i) {
228 static int cur = 0;
229 static int state = 0;
230 const int max_controls = 9;
231 const char *str = NULL;
232 const controls_menu_t *mm = (controls_menu_t*)m;
233 const char *captions[] = {
234 "Up: ", "Down: ", "Left: ", "Right: ", "Fire: ", "Jump: ", "Prev: ", "Next: ", "Press: "
235 };
236 assert(i >= 0 && i < max_controls);
237 switch (msg->type) {
238 case GM_ENTER:
239 state = 0;
240 break; /* default behavior */
241 case GM_LEAVE:
242 if (state == 0) {
243 break; /* default behavior */
244 } else {
245 state = 0;
246 return 1;
248 case GM_UP:
249 case GM_DOWN:
250 if (state == 0) {
251 break; /* default behavior */
252 } else {
253 return 1; /* ignore */
255 case GM_KEY:
256 if (state != 0 && msg->integer.i != KEY_UNKNOWN) {
257 mm->pl_keys[cur] = msg->integer.i;
258 state = 0;
260 return 1;
261 case GM_GETENTRY:
262 return GM_init_int0(msg, GM_SMALL_BUTTON, 0, 0, 0);
263 case GM_GETCAPTION:
264 return GM_init_str(msg, (char*)captions[i], strlen(captions[i]));
265 case GM_GETSTR:
266 if (state == 0 || i != cur) {
267 str = I_key_to_string(mm->pl_keys[i]);
268 } else {
269 str = "...";
271 return GM_init_str(msg, (char*)str, strlen(str));
272 case GM_SELECT:
273 state = state == 0 ? 1 : 0;
274 return 1;
276 return basic_menu_handler(msg, GM_BIG, "Player 1 controls", NULL, max_controls, &cur);
279 static int options_menu_handler (menu_msg_t *msg, const menu_t *m, int i) {
280 static int cur;
281 const menu_t *mm;
282 enum { VIDEO, SOUND, MUSIC, CONTROLS_1, CONTROLS_2, OPT__NUM__ };
283 static const controls_menu_t c1 = {
284 { controls_menu_handler },
285 &pl1.ku
286 };
287 static const controls_menu_t c2 = {
288 { controls_menu_handler },
289 &pl2.ku
290 };
291 static const simple_menu_t sm = {
292 GM_BIG, "Options", NULL,
294 { "Video", NULL },
295 { "Sound", NULL },
296 { "Music", NULL },
297 { "Controls 1", &c1.base },
298 { "Controls 2", &c2.base },
300 };
301 if (msg->type == GM_SELECT) {
302 switch (i) {
303 case VIDEO: mm = R_menu(); break;
304 case SOUND: mm = S_menu(); break;
305 case MUSIC: mm = MUS_menu(); break;
306 default: mm = NULL;
308 if (mm != NULL) {
309 return GM_push(mm);
312 return simple_menu_handler(msg, i, OPT__NUM__, &sm, &cur);
315 static int exit_menu_handler (menu_msg_t *msg, const menu_t *m, int i) {
316 static int cur;
317 enum { YES, NO, EXIT__NUM__ };
318 static const simple_menu_t sm = {
319 GM_SMALL, "You are sure?", NULL,
321 { "Yes", NULL },
322 { "No", NULL },
324 };
325 if (msg->type == GM_ENTER) {
326 return GM_say(rand() & 1 ? "_EXIT1" : "_EXIT2");
327 } else if (msg->type == GM_SELECT) {
328 switch (i) {
329 case YES:
330 MUS_free();
331 GM_stop();
332 Z_sound(S_get(qsnd[myrand(QSND_NUM)]), 255);
333 S_wait();
334 ERR_quit();
335 return 1;
336 case NO:
337 return GM_pop();
340 return simple_menu_handler(msg, i, EXIT__NUM__, &sm, &cur);
343 static int main_menu_handler (menu_msg_t *msg, const menu_t *m, int i) {
344 enum { NEWGAME, OLDGAME, SAVEGAME, OPTIONS, EXIT, MAIN__NUM__ };
345 static int cur;
346 static const menu_t hm[MAIN__NUM__] = {
347 { new_game_menu_handler },
348 { load_game_menu_handler },
349 { save_game_menu_handler },
350 { options_menu_handler},
351 { exit_menu_handler }
352 };
353 static const simple_menu_t sm = {
354 GM_BIG, "Menu", NULL,
356 { "New Game", &hm[NEWGAME] },
357 { "Load Game", &hm[OLDGAME] },
358 { "Save Game", &hm[SAVEGAME] },
359 { "Options", &hm[OPTIONS] },
360 { "Exit", &hm[EXIT] }
362 };
363 assert(i >= 0 && i < MAIN__NUM__);
364 return simple_menu_handler(msg, i, MAIN__NUM__, &sm, &cur);
367 static const menu_t main_menu = { &main_menu_handler };
369 int GM_push (const menu_t *m) {
370 menu_msg_t msg;
371 assert(m != NULL);
372 assert(stack_p >= -1);
373 assert(stack_p < MAX_STACK - 1);
374 stack_p += 1;
375 stack[stack_p].m = m;
376 msg.type = GM_ENTER;
377 GM_send_this(m, &msg);
378 return 1;
381 int GM_pop (void) {
382 menu_msg_t msg;
383 assert(stack_p >= 0);
384 stack_p -= 1;
385 msg.type = GM_LEAVE;
386 GM_send_this(stack[stack_p + 1].m, &msg);
387 return 1;
390 int GM_popall (void) {
391 int i;
392 for (i = 0; i >= -1; i--) {
393 GM_pop();
395 return 1;
398 const menu_t *GM_get (void) {
399 if (stack_p >= 0) {
400 return stack[stack_p].m;
401 } else {
402 return NULL;
406 static void GM_normalize_message (menu_msg_t *msg) {
407 switch (msg->type) {
408 case GM_SETINT:
409 msg->integer.i = min(max(msg->integer.i, msg->integer.a), msg->integer.b);
410 break;
411 case GM_SETSTR:
412 assert(msg->string.maxlen >= 0);
413 break;
417 int GM_send_this (const menu_t *m, menu_msg_t *msg) {
418 assert(m != NULL);
419 assert(msg != NULL);
420 if (m->handler != NULL) {
421 GM_normalize_message(msg);
422 return m->handler(msg, m, 0);
424 return 0;
427 int GM_send (const menu_t *m, int i, menu_msg_t *msg) {
428 assert(m != NULL);
429 assert(i >= 0);
430 assert(msg != NULL);
431 if (m->handler != NULL) {
432 GM_normalize_message(msg);
433 return m->handler(msg, m, i);
435 return 0;
438 void G_code (void) {
439 void *s;
440 s=csnd2;
441 if(memcmp(cbuf+32-5,"IDDQD",5)==0) {
442 PL_hit(&pl1,400,0,HIT_SOME);
443 if(_2pl) PL_hit(&pl2,400,0,HIT_SOME);
444 s=csnd1;
445 }else if(memcmp(cbuf+32-4,"TANK",4)==0) {
446 pl1.life=pl1.armor=200;pl1.drawst|=PL_DRAWARMOR|PL_DRAWLIFE;
447 if(_2pl) {pl2.life=pl2.armor=200;pl2.drawst|=PL_DRAWARMOR|PL_DRAWLIFE;}
448 }else if(memcmp(cbuf+32-8,"BULLFROG",8)==0) {
449 PL_JUMP=(PL_JUMP==10)?20:10;
450 }else if(memcmp(cbuf+32-8,"FORMULA1",8)==0) {
451 PL_RUN=(PL_RUN==8)?24:8;
452 }else if(memcmp(cbuf+32-5,"RAMBO",5)==0) {
453 pl1.ammo=pl1.shel=pl1.rock=pl1.cell=pl1.fuel=30000;
454 pl1.wpns=0x7FF;pl1.drawst|=PL_DRAWWPN|PL_DRAWKEYS;
455 pl1.keys=0x70;
456 if(_2pl) {
457 pl2.ammo=pl2.shel=pl2.rock=pl2.cell=pl1.fuel=30000;
458 pl2.wpns=0x7FF;pl2.drawst|=PL_DRAWWPN|PL_DRAWKEYS;
459 pl2.keys=0x70;
461 }else if(memcmp(cbuf+32-5,"UJHTW",5)==0) {
462 p_immortal=!p_immortal;
463 }else if(memcmp(cbuf+32-9,",TKSQJHTK",9)==0) {
464 p_fly=!p_fly;
465 }else if(memcmp(cbuf+32-6,"CBVCBV",6)==0) {
466 SW_cheat_open();
467 }else if(memcmp(cbuf+32-7,"GOODBYE",7)==0) {
468 g_exit=1;
469 }else if(memcmp(cbuf+32-9,"GJITKYF",7)==0) {
470 if(cbuf[30]>='0' && cbuf[30]<='9' && cbuf[31]>='0' && cbuf[31]<='9') {
471 g_map=(cbuf[30]=='0')?0:(cbuf[30]-'0')*10;
472 g_map+=(cbuf[31]=='0')?0:(cbuf[31]-'0');
473 G_start();
475 }else return;
476 memset(cbuf,0,32);
477 Z_sound(s,128);
480 static int x_strnlen (const char *s, int len) {
481 int i = 0;
482 while (i < len && s[i] != 0) {
483 i++;
485 return i;
488 static int state_for_anykey (int x) {
489 return x == GS_TITLE || x == GS_ENDSCR;
492 int GM_act (void) {
493 menu_msg_t msg;
494 int n, cur, type;
495 const menu_t *m = GM_get();
496 if (m == NULL) {
497 if (lastkey == KEY_ESCAPE || (state_for_anykey(g_st) && lastkey != KEY_UNKNOWN)) {
498 GM_push(&main_menu);
499 Z_sound(msnd3, 128);
501 } else {
502 /* 1. send key */
503 msg.type = GM_KEY;
504 GM_init_int0(&msg, lastkey, 0, 0, 0);
505 GM_send_this(m, &msg);
506 /* 2. send query */
507 msg.type = GM_QUERY;
508 if (GM_send_this(m, &msg)) {
509 /* 3. send getentry */
510 cur = msg.integer.i;
511 n = msg.integer.a;
512 msg.type = GM_GETENTRY;
513 if (GM_send(m, cur, &msg)) {
514 /* 4. send actions */
515 type = msg.integer.i;
516 switch (lastkey) {
517 case KEY_ESCAPE:
518 if (type == GM_TEXTFIELD && input) {
519 input = 0;
520 Y_disable_text_input();
521 msg.type = GM_CANCEL;
522 GM_send(m, cur, &msg);
523 } else {
524 GM_pop();
525 Z_sound(msnd4, 128);
527 break;
528 case KEY_UP:
529 case KEY_DOWN:
530 if (input == 0) {
531 msg.type = lastkey == KEY_UP ? GM_UP : GM_DOWN;
532 if (GM_send(m, cur, &msg)) {
533 Z_sound(msnd1, 128);
536 break;
537 case KEY_LEFT:
538 case KEY_RIGHT:
539 if (type == GM_SCROLLER) {
540 msg.integer.type = GM_GETINT;
541 if (GM_send(m, cur, &msg)) {
542 msg.integer.type = GM_SETINT;
543 msg.integer.i += lastkey == KEY_LEFT ? -msg.integer.s : msg.integer.s;
544 msg.integer.i = min(max(msg.integer.i, msg.integer.a), msg.integer.b);
545 if (GM_send(m, cur, &msg)) {
546 Z_sound(lastkey == KEY_LEFT ? msnd5 : msnd6, 255);
549 } else if (type == GM_TEXTFIELD) {
550 //if (input) {
551 // icur += lastkey == KEY_LEFT ? -1 : +1;
552 // icur = min(max(icur, 0), x_strnlen(ibuf, imax));
553 //}
555 break;
556 case KEY_BACKSPACE:
557 if (type == GM_TEXTFIELD) {
558 if (input && icur > 0) {
559 // FIXIT buffers in strncpy must not overlap
560 strncpy(&ibuf[icur - 1], &ibuf[icur], imax - icur);
561 ibuf[imax - 1] = 0;
562 icur -= 1;
565 break;
566 case KEY_RETURN:
567 if (type == GM_TEXTFIELD) {
568 if (input) {
569 input = 0;
570 Y_disable_text_input();
571 msg.type = GM_END;
572 msg.string.s = ibuf;
573 msg.string.maxlen = imax;
574 GM_send(m, cur, &msg);
575 } else {
576 msg.type = GM_GETSTR;
577 if (GM_send(m, cur, &msg)) {
578 imax = min(msg.string.maxlen, GM_MAX_INPUT);
579 strncpy(ibuf, msg.string.s, imax);
580 icur = x_strnlen(ibuf, imax);
581 } else {
582 memset(ibuf, 0, GM_MAX_INPUT);
583 imax = GM_MAX_INPUT;
584 icur = 0;
586 input = 1;
587 Y_enable_text_input();
588 msg.type = GM_BEGIN;
589 GM_send(m, cur, &msg);
591 Z_sound(msnd2, 128);
592 } else {
593 msg.type = GM_SELECT;
594 if (GM_send(m, cur, &msg)) {
595 Z_sound(msnd2, 128);
598 break;
603 lastkey = KEY_UNKNOWN;
604 return m != NULL;
607 void GM_input (int ch) {
608 if (ch != 0 && input) {
609 if (icur < imax) {
610 ibuf[icur] = ch;
611 icur += 1;
612 if (icur < imax) {
613 ibuf[icur] = 0;
619 void GM_key (int key, int down) {
620 int i;
621 if (down) {
622 lastkey = key;
623 if (!_2pl || cheat) {
624 for (i = 0; i < 31; i++) {
625 cbuf[i] = cbuf[i + 1];
627 if (key >= KEY_0 && key <= KEY_9) {
628 cbuf[31] = key - KEY_0 + '0';
629 } else if (key >= KEY_A && key <= KEY_Z) {
630 cbuf[31] = key - KEY_A + 'A';
631 } else {
632 cbuf[31] = 0;
638 void GM_init (void) {
639 int i;
640 char s[8];
641 static const char nm[QSND_NUM][6] = {
642 "CYBSIT", "KNTDTH", "MNPAIN", "PEPAIN", "SLOP", "MANSIT", "BOSPN", "VILACT",
643 "PLFALL", "BGACT", "BGDTH2", "POPAIN", "SGTATK", "VILDTH"
644 };
645 s[0] = 'D';
646 s[1] = 'S';
647 for (i = 0; i < QSND_NUM; ++i) {
648 memcpy(s + 2, nm[i], 6);
649 qsnd[i] = F_getresid(s);
651 csnd1 = Z_getsnd("HAHA1");
652 csnd2 = Z_getsnd("RADIO");
653 msnd1 = Z_getsnd("PSTOP");
654 msnd2 = Z_getsnd("PISTOL");
655 msnd3 = Z_getsnd("SWTCHN");
656 msnd4 = Z_getsnd("SWTCHX");
657 msnd5 = Z_getsnd("SUDI");
658 msnd6 = Z_getsnd("TUDI");
659 MUS_load("MENU");
660 MUS_start(0);