1 /* Copyright (C) 2020 SovietPony
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.
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.
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/>.
43 static byte pcolortab
[PCOLORN
] = {
44 0x18, 0x20, 0x40, 0x58, 0x60, 0x70, 0x80, 0xB0, 0xC0, 0xD0
46 static int p1color
= 5;
47 static int p2color
= 4;
55 static int stack_p
= -1;
57 #define GM_MAX_INPUT 24
58 char ibuf
[GM_MAX_INPUT
];
66 static int qsnd
[QSND_NUM
];
67 static snd_t
*csnd1
, *csnd2
, *msnd1
, *msnd2
, *msnd3
, *msnd4
, *msnd5
, *msnd6
;
71 static void GM_stop (void) {
82 static int GM_say (const char nm
[8]) {
83 snd_t
*snd
= S_load(nm
);
87 voc_ch
= S_play(voc
, 0, 255);
92 int GM_init_int0 (menu_msg_t
*msg
, int i
, int a
, int b
, int s
) {
101 int GM_init_int (menu_msg_t
*msg
, int i
, int a
, int b
, int s
) {
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
) {
113 msg
->string
.maxlen
= maxlen
;
117 int basic_menu_handler (menu_msg_t
*msg
, byte type
, char *title
, char *say
, int n
, int *cur
) {
119 assert(type
== GM_BIG
|| type
== GM_SMALL
);
120 assert(title
!= NULL
);
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;
133 int simple_menu_handler (menu_msg_t
*msg
, int i
, int n
, const simple_menu_t
*m
, int *cur
) {
136 assert(i
>= 0 && i
< n
);
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
) {
150 g_map
= level
? level
: 1;
152 pl1
.color
= pcolortab
[p1color
];
153 pl2
.color
= pcolortab
[p2color
];
158 static int new_game_menu_handler (menu_msg_t
*msg
, const menu_t
*m
, int i
) {
160 enum { ONEPLAYER
, TWOPLAYERS
, DEATHMATCH
, __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
},
169 if (msg
->type
== GM_SELECT
) {
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
);
177 return simple_menu_handler(msg
, i
, __NUM__
, &sm
, &cur
);
180 static int load_game_menu_handler (menu_msg_t
*msg
, const menu_t
*m
, int i
) {
182 const int max_slots
= 7;
183 assert(i
>= 0 && i
< max_slots
);
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);
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
) {
200 const int max_slots
= 7;
201 assert(i
>= 0 && i
< max_slots
);
204 if (g_st
== GS_GAME
) {
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);
213 if (g_st
== GS_GAME
) {
214 assert(msg
->string
.maxlen
>= 24);
215 F_savegame(i
, msg
->string
.s
);
219 return basic_menu_handler(msg
, GM_BIG
, "Save game", "_SAVGAME", max_slots
, &cur
);
222 typedef struct controls_menu_t
{
227 static int controls_menu_handler (menu_msg_t
*msg
, const menu_t
*m
, int i
) {
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: "
236 assert(i
>= 0 && i
< max_controls
);
240 break; /* default behavior */
243 break; /* default behavior */
251 break; /* default behavior */
253 return 1; /* ignore */
256 if (state
!= 0 && msg
->integer
.i
!= KEY_UNKNOWN
) {
257 mm
->pl_keys
[cur
] = msg
->integer
.i
;
262 return GM_init_int0(msg
, GM_SMALL_BUTTON
, 0, 0, 0);
264 return GM_init_str(msg
, (char*)captions
[i
], strlen(captions
[i
]));
266 str
= state
== 0 || i
!= cur
? I_key_to_string(mm
->pl_keys
[i
]) : "...";
267 return GM_init_str(msg
, (char*)str
, strlen(str
));
269 state
= state
== 0 ? 1 : 0;
272 return basic_menu_handler(msg
, GM_BIG
, "Player 1 controls", NULL
, max_controls
, &cur
);
275 static int options_menu_handler (menu_msg_t
*msg
, const menu_t
*m
, int i
) {
278 enum { VIDEO
, SOUND
, MUSIC
, CONTROLS_1
, CONTROLS_2
, __NUM__
};
279 static const controls_menu_t c1
= {
280 { controls_menu_handler
},
283 static const controls_menu_t c2
= {
284 { controls_menu_handler
},
287 static const simple_menu_t sm
= {
288 GM_BIG
, "Options", NULL
,
293 { "Controls 1", &c1
.base
},
294 { "Controls 2", &c2
.base
},
297 if (msg
->type
== GM_SELECT
) {
299 case VIDEO
: mm
= R_menu(); break;
300 case SOUND
: mm
= S_menu(); break;
301 case MUSIC
: mm
= MUS_menu(); break;
308 return simple_menu_handler(msg
, i
, __NUM__
, &sm
, &cur
);
311 static int exit_menu_handler (menu_msg_t
*msg
, const menu_t
*m
, int i
) {
313 enum { YES
, NO
, __NUM__
};
314 static const simple_menu_t sm
= {
315 GM_SMALL
, "You are sure?", NULL
,
321 if (msg
->type
== GM_ENTER
) {
322 return GM_say(rand() & 1 ? "_EXIT1" : "_EXIT2");
323 } else if (msg
->type
== GM_SELECT
) {
328 Z_sound(S_get(qsnd
[myrand(QSND_NUM
)]), 255);
336 return simple_menu_handler(msg
, i
, __NUM__
, &sm
, &cur
);
339 static int main_menu_handler (menu_msg_t
*msg
, const menu_t
*m
, int i
) {
340 enum { NEWGAME
, OLDGAME
, SAVEGAME
, OPTIONS
, EXIT
, __NUM__
};
341 assert(i
>= 0 && i
< __NUM__
);
343 static const menu_t hm
[__NUM__
] = {
344 { new_game_menu_handler
},
345 { load_game_menu_handler
},
346 { save_game_menu_handler
},
347 { options_menu_handler
},
348 { exit_menu_handler
}
350 static const simple_menu_t sm
= {
351 GM_BIG
, "Menu", NULL
,
353 { "New Game", &hm
[NEWGAME
] },
354 { "Load Game", &hm
[OLDGAME
] },
355 { "Save Game", &hm
[SAVEGAME
] },
356 { "Options", &hm
[OPTIONS
] },
357 { "Exit", &hm
[EXIT
] }
360 return simple_menu_handler(msg
, i
, __NUM__
, &sm
, &cur
);
363 static const menu_t main_menu
= { &main_menu_handler
};
365 int GM_push (const menu_t
*m
) {
367 assert(stack_p
>= -1);
368 assert(stack_p
< MAX_STACK
- 1);
371 stack
[stack_p
].m
= m
;
373 GM_send_this(m
, &msg
);
378 assert(stack_p
>= 0);
382 GM_send_this(stack
[stack_p
+ 1].m
, &msg
);
386 int GM_popall (void) {
388 for (i
= 0; i
>= -1; i
--) {
394 const menu_t
*GM_get (void) {
396 return stack
[stack_p
].m
;
402 static void GM_normalize_message (menu_msg_t
*msg
) {
405 msg
->integer
.i
= min(max(msg
->integer
.i
, msg
->integer
.a
), msg
->integer
.b
);
408 assert(msg
->string
.maxlen
>= 0);
413 int GM_send_this (const menu_t
*m
, menu_msg_t
*msg
) {
416 if (m
->handler
!= NULL
) {
417 GM_normalize_message(msg
);
418 return m
->handler(msg
, m
, 0);
423 int GM_send (const menu_t
*m
, int i
, menu_msg_t
*msg
) {
427 if (m
->handler
!= NULL
) {
428 GM_normalize_message(msg
);
429 return m
->handler(msg
, m
, i
);
437 if(memcmp(cbuf
+32-5,"IDDQD",5)==0) {
438 PL_hit(&pl1
,400,0,HIT_SOME
);
439 if(_2pl
) PL_hit(&pl2
,400,0,HIT_SOME
);
441 }else if(memcmp(cbuf
+32-4,"TANK",4)==0) {
442 pl1
.life
=pl1
.armor
=200;pl1
.drawst
|=PL_DRAWARMOR
|PL_DRAWLIFE
;
443 if(_2pl
) {pl2
.life
=pl2
.armor
=200;pl2
.drawst
|=PL_DRAWARMOR
|PL_DRAWLIFE
;}
444 }else if(memcmp(cbuf
+32-8,"BULLFROG",8)==0) {
445 PL_JUMP
=(PL_JUMP
==10)?20:10;
446 }else if(memcmp(cbuf
+32-8,"FORMULA1",8)==0) {
447 PL_RUN
=(PL_RUN
==8)?24:8;
448 }else if(memcmp(cbuf
+32-5,"RAMBO",5)==0) {
449 pl1
.ammo
=pl1
.shel
=pl1
.rock
=pl1
.cell
=pl1
.fuel
=30000;
450 pl1
.wpns
=0x7FF;pl1
.drawst
|=PL_DRAWWPN
|PL_DRAWKEYS
;
453 pl2
.ammo
=pl2
.shel
=pl2
.rock
=pl2
.cell
=pl1
.fuel
=30000;
454 pl2
.wpns
=0x7FF;pl2
.drawst
|=PL_DRAWWPN
|PL_DRAWKEYS
;
457 }else if(memcmp(cbuf
+32-5,"UJHTW",5)==0) {
458 p_immortal
=!p_immortal
;
459 }else if(memcmp(cbuf
+32-9,",TKSQJHTK",9)==0) {
461 }else if(memcmp(cbuf
+32-6,"CBVCBV",6)==0) {
463 }else if(memcmp(cbuf
+32-7,"GOODBYE",7)==0) {
465 }else if(memcmp(cbuf
+32-9,"GJITKYF",7)==0) {
466 if(cbuf
[30]>='0' && cbuf
[30]<='9' && cbuf
[31]>='0' && cbuf
[31]<='9') {
467 g_map
=(cbuf
[30]=='0')?0:(cbuf
[30]-'0')*10;
468 g_map
+=(cbuf
[31]=='0')?0:(cbuf
[31]-'0');
476 static int x_strnlen (const char *s
, int len
) {
478 while (i
< len
&& s
[i
] != 0) {
484 static int state_for_anykey (int x
) {
485 return x
== GS_TITLE
|| x
== GS_ENDSCR
;
491 const menu_t
*m
= GM_get();
493 if (lastkey
== KEY_ESCAPE
|| (state_for_anykey(g_st
) && lastkey
!= KEY_UNKNOWN
)) {
500 GM_init_int0(&msg
, lastkey
, 0, 0, 0);
501 GM_send_this(m
, &msg
);
504 if (GM_send_this(m
, &msg
)) {
505 /* 3. send getentry */
508 msg
.type
= GM_GETENTRY
;
509 if (GM_send(m
, cur
, &msg
)) {
510 /* 4. send actions */
511 type
= msg
.integer
.i
;
514 if (type
== GM_TEXTFIELD
&& input
) {
516 Y_disable_text_input();
517 msg
.type
= GM_CANCEL
;
518 GM_send(m
, cur
, &msg
);
527 msg
.type
= lastkey
== KEY_UP
? GM_UP
: GM_DOWN
;
528 if (GM_send(m
, cur
, &msg
)) {
535 if (type
== GM_SCROLLER
) {
536 msg
.integer
.type
= GM_GETINT
;
537 if (GM_send(m
, cur
, &msg
)) {
538 msg
.integer
.type
= GM_SETINT
;
539 msg
.integer
.i
+= lastkey
== KEY_LEFT
? -msg
.integer
.s
: msg
.integer
.s
;
540 msg
.integer
.i
= min(max(msg
.integer
.i
, msg
.integer
.a
), msg
.integer
.b
);
541 if (GM_send(m
, cur
, &msg
)) {
542 Z_sound(lastkey
== KEY_LEFT
? msnd5
: msnd6
, 255);
545 } else if (type
== GM_TEXTFIELD
) {
547 // icur += lastkey == KEY_LEFT ? -1 : +1;
548 // icur = min(max(icur, 0), x_strnlen(ibuf, imax));
553 if (type
== GM_TEXTFIELD
) {
554 if (input
&& icur
> 0) {
555 // FIXIT buffers in strncpy must not overlap
556 strncpy(&ibuf
[icur
- 1], &ibuf
[icur
], imax
- icur
);
563 if (type
== GM_TEXTFIELD
) {
566 Y_disable_text_input();
569 msg
.string
.maxlen
= imax
;
570 GM_send(m
, cur
, &msg
);
572 msg
.type
= GM_GETSTR
;
573 if (GM_send(m
, cur
, &msg
)) {
574 imax
= min(msg
.string
.maxlen
, GM_MAX_INPUT
);
575 strncpy(ibuf
, msg
.string
.s
, imax
);
576 icur
= x_strnlen(ibuf
, imax
);
578 memset(ibuf
, 0, GM_MAX_INPUT
);
583 Y_enable_text_input();
585 GM_send(m
, cur
, &msg
);
589 msg
.type
= GM_SELECT
;
590 if (GM_send(m
, cur
, &msg
)) {
599 lastkey
= KEY_UNKNOWN
;
603 void GM_input (int ch
) {
604 if (ch
!= 0 && input
) {
615 void GM_key (int key
, int down
) {
619 if (!_2pl
|| cheat
) {
620 for (i
= 0; i
< 31; i
++) {
621 cbuf
[i
] = cbuf
[i
+ 1];
623 if (key
>= KEY_0
&& key
<= KEY_9
) {
624 cbuf
[31] = key
- KEY_0
+ '0';
625 } else if (key
>= KEY_A
&& key
<= KEY_Z
) {
626 cbuf
[31] = key
- KEY_A
+ 'A';
634 void GM_init (void) {
637 static const char nm
[QSND_NUM
][6] = {
638 "CYBSIT", "KNTDTH", "MNPAIN", "PEPAIN", "SLOP", "MANSIT", "BOSPN", "VILACT",
639 "PLFALL", "BGACT", "BGDTH2", "POPAIN", "SGTATK", "VILDTH"
643 for (i
= 0; i
< QSND_NUM
; ++i
) {
644 memcpy(s
+ 2, nm
[i
], 6);
645 qsnd
[i
] = F_getresid(s
);
647 csnd1
= Z_getsnd("HAHA1");
648 csnd2
= Z_getsnd("RADIO");
649 msnd1
= Z_getsnd("PSTOP");
650 msnd2
= Z_getsnd("PISTOL");
651 msnd3
= Z_getsnd("SWTCHN");
652 msnd4
= Z_getsnd("SWTCHX");
653 msnd5
= Z_getsnd("SUDI");
654 msnd6
= Z_getsnd("TUDI");