DEADSOFTWARE

sound: move sound/music configuration to sound driver
[flatwaifu.git] / src / menu.c
index bd225a4acace832a199389b15cec09d546973d9c..7286a45375a11e4a271996357ad3b9150b93f9d6 100644 (file)
 */
 
 #include "glob.h"
-#include <stdio.h>
-#include <string.h>
-#include <stdlib.h>
 #include "files.h"
 #include "memory.h"
 #include "error.h"
-#include "keyb.h"
 #include "sound.h"
 #include "view.h"
 #include "player.h"
 #include "player.h"
 #include "sound.h"
 #include "music.h"
+#include "input.h"
+#include "system.h"
 
-#include <SDL.h>
-#include <sys/stat.h>
+#include "save.h"
 
-#define QSND_NUM 14
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <assert.h>
 
-enum{HIT100,ARMOR,JUMP,WPNS,IMMORTAL,SPEED,OPEN,EXIT};
+#define PCOLORN 10
+static byte pcolortab[PCOLORN] = {
+  0x18, 0x20, 0x40, 0x58, 0x60, 0x70, 0x80, 0xB0, 0xC0, 0xD0
+};
+static int p1color = 5;
+static int p2color = 4;
 
-static byte panim[] = "BBDDAACCDDAABBDDAACCDDAABBDDAACCDDAAEEEEEFEFEFEFEFEFEFEFEFEFEEEEE";
-byte *panimp = panim;
 byte _warp;
 
-byte pcolortab[PCOLORN] = {
-  0x18, 0x20, 0x40, 0x58, 0x60, 0x70, 0x80, 0xB0, 0xC0, 0xD0
+#define MAX_STACK 8
+static struct {
+  const menu_t *m;
+} stack[MAX_STACK];
+static int stack_p = -1;
+
+#define GM_MAX_INPUT 24
+char ibuf[GM_MAX_INPUT];
+byte input;
+int icur;
+int imax;
+static byte cbuf[32];
+short lastkey;
+
+#define QSND_NUM 14
+static int qsnd[QSND_NUM];
+static snd_t *csnd1, *csnd2, *msnd1, *msnd2, *msnd3, *msnd4, *msnd5, *msnd6;
+static snd_t *voc;
+static int voc_ch;
+
+static void GM_stop (void) {
+  if (voc != NULL) {
+    if (voc_ch) {
+      S_stop(voc_ch);
+      voc_ch = 0;
+    }
+    S_free(voc);
+    voc = NULL;
+  }
+}
+
+static int GM_say (const char nm[8]) {
+  snd_t *snd = S_load(nm);
+  if (snd) {
+    GM_stop();
+    voc = S_load(nm);
+    voc_ch = S_play(voc, 0, 255);
+  }
+  return 1;
+}
+
+int GM_init_int0 (menu_msg_t *msg, int i, int a, int b, int s) {
+  assert(msg != NULL);
+  msg->integer.i = i;
+  msg->integer.a = a;
+  msg->integer.b = b;
+  msg->integer.s = s;
+  return 1;
+}
+
+int GM_init_int (menu_msg_t *msg, int i, int a, int b, int s) {
+  assert(msg != NULL);
+  assert(a <= b);
+  assert(s >= 0);
+  return GM_init_int0(msg, min(max(i, a), b), a, b, s);
+}
+
+int GM_init_str (menu_msg_t *msg, char *str, int maxlen) {
+  assert(msg != NULL);
+  assert(str != NULL);
+  assert(maxlen >= 0);
+  msg->string.s = str;
+  msg->string.maxlen = maxlen;
+  return 1;
+}
+
+int basic_menu_handler (menu_msg_t *msg, byte type, char *title, char *say, int n, int *cur) {
+  assert(msg != NULL);
+  assert(type == GM_BIG || type == GM_SMALL);
+  assert(title != NULL);
+  assert(n >= 0);
+  assert(cur != NULL);
+  switch (msg->type) {
+    case GM_QUERY: return GM_init_int0(msg, *cur, n, n, type);
+    case GM_GETTITLE: return GM_init_str(msg, title, strlen(title));
+    case GM_ENTER: return say ? GM_say(say) : 1;
+    case GM_UP: *cur = GM_CYCLE(*cur - 1, 0, n - 1); return 1;
+    case GM_DOWN: *cur = GM_CYCLE(*cur + 1, 0, n - 1); return 1;
+  }
+  return 0;
+}
+
+int simple_menu_handler (menu_msg_t *msg, int i, int n, const simple_menu_t *m, int *cur) {
+  assert(msg != NULL);
+  assert(n >= 0);
+  assert(i >= 0 && i < n);
+  assert(m != NULL);
+  assert(cur != NULL);
+  switch (msg->type) {
+    case GM_GETENTRY: return GM_init_int0(msg, m->type == GM_SMALL ? GM_SMALL_BUTTON : GM_BUTTON, 0, 0, 0);
+    case GM_GETCAPTION: return GM_init_str(msg, m->entries[i].caption, strlen(m->entries[i].caption));
+    case GM_SELECT: return m->entries[i].submenu ? GM_push(m->entries[i].submenu) : 1;
+  }
+  return basic_menu_handler(msg, m->type, m->title, m->say, n, cur);
+}
+
+static int start_game (int twoplayers, int dm, int level) {
+  _2pl = twoplayers;
+  g_dm = dm;
+  g_map = level ? level : 1;
+  PL_reset();
+  pl1.color = pcolortab[p1color];
+  pl2.color = pcolortab[p2color];
+  G_start();
+  return GM_popall();
+}
+
+static int new_game_menu_handler (menu_msg_t *msg, const menu_t *m, void *data, int i) {
+  static int cur;
+  enum { ONEPLAYER, TWOPLAYERS, DEATHMATCH, __NUM__ };
+  static const simple_menu_t sm = {
+    GM_BIG, "New Game", "_NEWGAME",
+    {
+      { "One Player", NULL },
+      { "Two Players", NULL },
+      { "Deathmatch", NULL },
+    }
+  };
+  if (msg->type == GM_SELECT) {
+    switch (i) {
+      case ONEPLAYER: GM_say("_1PLAYER"); return start_game(0, 0, _warp);
+      case TWOPLAYERS: GM_say("_2PLAYER"); return start_game(1, 0, _warp);
+      case DEATHMATCH: GM_say("_DM"); return start_game(1, 1, _warp);
+      // GM_say("_COOP");
+    }
+  }
+  return simple_menu_handler(msg, i, __NUM__, &sm, &cur);
+}
+
+static const menu_t new_game_menu = {
+  NULL, &new_game_menu_handler
 };
-int p1color = 5;
-int p2color = 4;
 
-char ibuf[24];
-byte input=0;
-static int icur;
+static int load_game_menu_handler (menu_msg_t *msg, const menu_t *m, void *data, int i) {
+  static int cur;
+  const int max_slots = 7;
+  assert(i >= 0 && i < max_slots);
+  switch (msg->type) {
+    case GM_ENTER: F_getsavnames(); break;
+    case GM_GETENTRY: return GM_init_int0(msg, GM_TEXTFIELD_BUTTON, 0, 0, 0);
+    case GM_GETSTR: return GM_init_str(msg, (char*)savname[i], 24);
+    case GM_SELECT:
+      if (savok[i]) {
+        load_game(i);
+        GM_popall();
+      }
+      return 1;
+  }
+  return basic_menu_handler(msg, GM_BIG, "Load game", "_OLDGAME", max_slots, &cur);
+}
 
-enum{MENU,MSG};
-enum{CANCEL,NEWGAME,LOADGAME,SAVEGAME,OPTIONS,QUITGAME,QUIT,ENDGAME,ENDGM,
-  PLR1,PLR2,COOP,DM,VOLUME,GAMMA,LOAD,SAVE,PLCOLOR,PLCEND,MUSIC,INTERP,
-  SVOLM,SVOLP,MVOLM,MVOLP,GAMMAM,GAMMAP,PL1CM,PL1CP,PL2CM,PL2CP};
+static const menu_t load_game_menu = {
+  NULL, &load_game_menu_handler
+};
 
-#ifndef DEMO
-static int qsnd[QSND_NUM];
-#endif
-
-static char *main_txt[]={
-  "NEW GAME","LOAD GAME","SAVE GAME","OPTIONS","EXIT"
-},*opt_txt[]={
-  "RESTART","VOLUME","BRIGHTNESS","MUSIC","FULLSCREEN:"
-},*ngplr_txt[]={
-  "ONE PLAYER","TWO PLAYERS"
-},*ngdm_txt[]={
-  "COOPERATIVE","DEATHMATCH"
-},*vol_txt[]={
-  "SOUND","MUSIC"
-},*plcolor_txt[]={
-  "FIRST","SECOND"
-},*gamma_txt[]={
-  ""
+static int save_game_menu_handler (menu_msg_t *msg, const menu_t *m, void *data, int i) {
+  static int cur;
+  const int max_slots = 7;
+  assert(i >= 0 && i < max_slots);
+  switch (msg->type) {
+    case GM_ENTER:
+      if (g_st == GS_GAME) {
+        F_getsavnames();
+        break;
+      } else {
+        return GM_pop();
+      }
+    case GM_GETENTRY: return GM_init_int0(msg, GM_TEXTFIELD, 0, 0, 0);
+    case GM_GETSTR: return GM_init_str(msg, (char*)savname[i], 24);
+    case GM_END:
+      if (g_st == GS_GAME) {
+        assert(msg->string.maxlen >= 24);
+        F_savegame(i, msg->string.s);
+      }
+      return GM_popall();
+  }
+  return basic_menu_handler(msg, GM_BIG, "Save game", "_SAVGAME", max_slots, &cur);
+}
+
+static const menu_t save_game_menu = {
+  NULL, &save_game_menu_handler
+};
+
+static int options_menu_handler (menu_msg_t *msg, const menu_t *m, void *data, int i) {
+  static int cur;
+  const menu_t *mm;
+  enum { VIDEO, SOUND, MUSIC, __NUM__ };
+  static const simple_menu_t sm = {
+    GM_BIG, "Options", NULL,
+    {
+      { "Video", NULL },
+      { "Sound", NULL },
+      { "Music", NULL },
+    }
+  };
+  if (msg->type == GM_SELECT) {
+    switch (i) {
+      case VIDEO: mm = R_menu(); break;
+      case SOUND: mm = S_menu(); break;
+      case MUSIC: mm = MUS_menu(); break;
+      default: mm = NULL;
+    }
+    if (mm != NULL) {
+      return GM_push(mm);
+    }
+  }
+  return simple_menu_handler(msg, i, __NUM__, &sm, &cur);
+}
+
+static const menu_t options_menu = {
+  NULL, &options_menu_handler
 };
 
-static byte main_typ[]={
-  NEWGAME,LOADGAME,SAVEGAME,OPTIONS,QUITGAME
-},ngplr_typ[]={
-  PLR1,PLR2
-},ngdm_typ[]={
-  COOP,DM
-},opt_typ[]={
-  ENDGAME,VOLUME,GAMMA,MUSIC,INTERP
-},quit_typ[]={
-  QUIT,CANCEL
-},endgm_typ[]={
-  ENDGM,CANCEL
-},vol_typ[]={
-  SVOLM,MVOLM
-},plcolor_typ[]={
-  PL1CM,PL2CM
-},gamma_typ[]={
-  GAMMAM
-},load_typ[]={
-  LOAD,LOAD,LOAD,LOAD,LOAD,LOAD,LOAD
-},save_typ[]={
-  SAVE,SAVE,SAVE,SAVE,SAVE,SAVE,SAVE
+static int exit_menu_handler (menu_msg_t *msg, const menu_t *m, void *data, int i) {
+  static int cur;
+  enum { YES, NO, __NUM__ };
+  static const simple_menu_t sm = {
+    GM_SMALL, "You are sure?", NULL,
+    {
+      { "Yes", NULL },
+      { "No", NULL },
+    }
+  };
+  if (msg->type == GM_ENTER) {
+    return GM_say(rand() & 1 ? "_EXIT1" : "_EXIT2");
+  } else if (msg->type == GM_SELECT) {
+    switch (i) {
+      case YES:
+        MUS_free();
+        GM_stop();
+        Z_sound(S_get(qsnd[myrand(QSND_NUM)]), 255);
+        S_wait();
+        ERR_quit();
+        return 1;
+      case NO:
+        return GM_pop();
+    }
+  }
+  return simple_menu_handler(msg, i, __NUM__, &sm, &cur);
+}
+
+static const menu_t exit_menu = {
+  NULL, &exit_menu_handler
 };
 
-menu_t main_mnu={
-  MENU,5,0,80,"MENU",main_txt,main_typ
-},opt_mnu={
-  MENU,5,0,75,"OPTIONS",opt_txt,opt_typ
-},ngplr_mnu={
-  MENU,2,0,90,"NEW GAME",ngplr_txt,ngplr_typ
-},ngdm_mnu={
-  MENU,2,0,90,"GAME TYPE",ngdm_txt,ngdm_typ
-},vol_mnu={
-  MENU,2,0,40,"VOLUME",vol_txt,vol_typ
-},plcolor_mnu={
-  MENU,2,0,90,"COLOR",plcolor_txt,plcolor_typ
-},gamma_mnu={
-  MENU,1,0,85,"BRIGHTNESS",gamma_txt,gamma_typ
-},load_mnu={
-  MENU,7,0,85,"LOAD GAME",NULL,load_typ
-},save_mnu={
-  MENU,7,0,85,"SAVE GAME",NULL,save_typ
-},quit1_msg={
-  MSG,0,0,0,"ARE YOU SURE?",NULL,quit_typ
-},quit2_msg={
-  MSG,0,0,0,"ARE YOU SURE?",NULL,quit_typ
-},quit3_msg={
-  MSG,0,0,0,"ARE YOU SURE?",NULL,quit_typ
-},endgm_msg={
-  MSG,0,0,0,"RESTART LEVEL?",NULL,endgm_typ
+static int main_menu_handler (menu_msg_t *msg, const menu_t *m, void *data, int i) {
+  static int cur;
+  enum { NEWGAME, OLDGAME, SAVEGAME, OPTIONS, EXIT, __NUM__ };
+  assert(i >= 0 && i < __NUM__);
+  static const simple_menu_t sm = {
+    GM_BIG, "Menu", NULL,
+    {
+      { "New Game", &new_game_menu },
+      { "Load Game", &load_game_menu },
+      { "Save Game", &save_game_menu },
+      { "Options", &options_menu },
+      { "Exit", &exit_menu },
+    }
+  };
+  return simple_menu_handler(msg, i, __NUM__, &sm, &cur);
+}
+
+static const menu_t main_menu = {
+  NULL, &main_menu_handler
 };
 
-static menu_t *qmsg[3]={&quit1_msg,&quit2_msg,&quit3_msg};
+int GM_push (const menu_t *m) {
+  assert(m != NULL);
+  assert(stack_p >= -1);
+  assert(stack_p < MAX_STACK - 1);
+  menu_msg_t msg;
+  stack_p += 1;
+  stack[stack_p].m = m;
+  msg.type = GM_ENTER;
+  GM_send_this(m, &msg);
+  return 1;
+}
 
-menu_t *mnu=NULL;
-byte gm_redraw=0;
+int GM_pop (void) {
+  assert(stack_p >= 0);
+  menu_msg_t msg;
+  stack_p -= 1;
+  msg.type = GM_LEAVE;
+  GM_send_this(stack[stack_p + 1].m, &msg);
+  return 1;
+}
 
-short lastkey=0;
-static void *csnd1,*csnd2,*msnd1,*msnd2,*msnd3,*msnd4,*msnd5,*msnd6;
-static int movsndt=0;
-static byte cbuf[32];
+int GM_popall (void) {
+  int i;
+  for (i = 0; i >= -1; i--) {
+    GM_pop();
+  }
+  return 1;
+}
 
-static snd_t *voc=NULL;
-static int voc_ch=0;
+const menu_t *GM_get (void) {
+  if (stack_p >= 0) {
+    return stack[stack_p].m;
+  } else {
+    return NULL;
+  }
+}
 
-static void GMV_stop (void) {
-  if(voc) {
-    if(voc_ch) {S_stop(voc_ch);voc_ch=0;}
-    free(voc);voc=NULL;
+static void GM_normalize_message (menu_msg_t *msg) {
+  switch (msg->type) {
+    case GM_SETINT:
+      msg->integer.i = min(max(msg->integer.i, msg->integer.a), msg->integer.b);
+      break;
+    case GM_SETSTR:
+      assert(msg->string.maxlen >= 0);
+      break;
   }
 }
 
-void GMV_say (char *nm) {
-  int r,len;
-  snd_t *p;
-  byte *d;
-
-  if((r=F_findres(nm))==-1) return;
-  if(!(p=malloc((len=F_getreslen(r))+16))) return;
-  p->len=len;p->rate=11000;
-  p->lstart=p->llen=0;
-  GMV_stop();
-  F_loadres(r,p+1,0,len);
-  for(d=(byte*)(p+1);len;--len,++d) *d^=128;
-  voc=p;
-  voc_ch=S_play(voc,-1,1024,255);
+int GM_send_this (const menu_t *m, menu_msg_t *msg) {
+  assert(m != NULL);
+  assert(msg != NULL);
+  if (m->handler != NULL) {
+    GM_normalize_message(msg);
+    return m->handler(msg, m, m->data, 0);
+  }
+  return 0;
 }
 
-static void GM_set (menu_t *m) {
-  mnu=m;gm_redraw=1;
-  if(g_st==GS_GAME) {
-       //V_setrect(0,SCRW,0,SCRH);V_clr(0,SCRW,0,SCRH,0);//V_setrect(0,320,0,200);V_clr(0,320,0,200,0);
-       //if(_2pl) {V_setrect(SCRW-120,120,0,SCRH);w_o=0;Z_clrst();w_o=SCRH/2;Z_clrst();}//if(_2pl) {V_setrect(200,120,0,200);w_o=0;Z_clrst();w_o=100;Z_clrst();}
-       //else {V_setrect(SCRW-120,120,0,SCRH);w_o=0;Z_clrst();}//else {V_setrect(200,120,50,100);w_o=50;Z_clrst();}
-       //pl1.drawst=pl2.drawst=0xFF;V_setrect(0,SCRW,0,SCRH);//V_setrect(0,320,0,200);
+int GM_send (const menu_t *m, int i, menu_msg_t *msg) {
+  assert(m != NULL);
+  assert(i >= 0);
+  assert(msg != NULL);
+  if (m->handler != NULL) {
+    GM_normalize_message(msg);
+    return m->handler(msg, m, m->data, i);
   }
+  return 0;
 }
 
 void G_code (void) {
@@ -227,323 +432,179 @@ void G_code (void) {
   Z_sound(s,128);
 }
 
-static void GM_command (int c) {
-  switch(c) {
-    case CANCEL:
-      GM_set(NULL);break;
-    case INTERP:
-      R_toggle_fullscreen();
-      GM_set(mnu);
-      break;
-    case MUSIC:
-      F_freemus();
-      F_nextmus(g_music);
-      F_loadmus(g_music);
-      S_startmusic(music_time*2);
-      GM_set(mnu);
-      break;
-    case NEWGAME:
-      GMV_say("_NEWGAME");
-      GM_set(&ngplr_mnu);break;
-    case PLR2:
-      GMV_say("_2PLAYER");
-      GM_set(&ngdm_mnu);break;
-    case PLR1:
-      GMV_say("_1PLAYER");
-      ngdm_mnu.cur=0;
-    case COOP: case DM:
-      if(c==COOP) GMV_say("_COOP");
-      else if(c==DM) GMV_say("_DM");
-      if(c!=PLR1) {GM_set(&plcolor_mnu);break;}
-    case PLCEND:
-      _2pl=ngplr_mnu.cur;
-      g_dm=ngdm_mnu.cur;
-      g_map=(_warp)?_warp:1;
-      PL_reset();
-      if(_2pl) {
-        pl1.color=pcolortab[p1color];
-        pl2.color=pcolortab[p2color];
-      }else pl1.color=0x70;
-      G_start();
-      GM_set(NULL);break;
-    case OPTIONS:
-      GMV_say("_RAZNOE");
-      GM_set(&opt_mnu);break;
-    case LOADGAME:
-      GMV_say("_OLDGAME");
-      F_getsavnames();GM_set(&load_mnu);break;
-    case SAVEGAME:
-      if(g_st!=GS_GAME) break;
-      GMV_say("_SAVEGAM");
-      F_getsavnames();GM_set(&save_mnu);break;
-    case SAVE:
-         input=1;memcpy(ibuf,savname[save_mnu.cur],24);icur=strlen(ibuf);
-         GM_set(mnu);break;
-    case LOAD:
-         if(!savok[load_mnu.cur]) break;
-         load_game(load_mnu.cur);
-         GM_set(NULL);break;
-       case VOLUME:
-         GMV_say("_VOLUME");
-         GM_set(&vol_mnu);break;
-       case GAMMA:
-         GMV_say("_GAMMA");
-         GM_set(&gamma_mnu);break;
-       case QUITGAME:
-         GMV_say((rand()&1)?"_EXIT1":"_EXIT2");
-         GM_set(qmsg[myrand(3)]);break;
-       case ENDGAME:
-         if(g_st!=GS_GAME) break;
-         GMV_say("_RESTART");
-         GM_set(&endgm_msg);break;
-       case QUIT:
-         F_freemus();
-         GMV_stop();
-#ifndef DEMO
-         c=Z_sound(M_lock(qsnd[myrand(QSND_NUM)]),256);//for(c=(Z_sound(M_lock(qsnd[random2(QSND_NUM)]),256)+9)<<16,timer=0;timer<c;);
-          S_wait();
-#endif
-         ERR_quit();break;
-    case ENDGM:
-         PL_reset();G_start();
-         GM_set(NULL);break;
-       case PL1CM:
-         if(--p1color<0) p1color=PCOLORN-1; break;
-       case PL1CP:
-         if(++p1color>=PCOLORN) p1color=0; break;
-       case PL2CM:
-         if(--p2color<0) p2color=PCOLORN-1; break;
-       case PL2CP:
-         if(++p2color>=PCOLORN) p2color=0; break;
-       case SVOLM:
-         S_volume(snd_vol-8);break;
-       case SVOLP:
-         S_volume(snd_vol+8);break;
-       case MVOLM:
-         S_volumemusic(mus_vol-8);break;
-       case MVOLP:
-         S_volumemusic(mus_vol+8);break;
-       case GAMMAM:
-    R_setgamma(R_getgamma() - 1);
-    break;
-       case GAMMAP:
-    R_setgamma(R_getgamma() + 1);
-    break;
+static int strnlen (const char *s, int len) {
+  int i = 0;
+  while (i < len && s[i] != 0) {
+    i++;
   }
+  return i;
 }
 
-struct {
-    int keysym;
-    byte ch;
-} keychar[] = {
-    {SDLK_SPACE, ' '},
-    {SDLK_0, '0'},
-    {SDLK_1, '1'},
-    {SDLK_2, '2'},
-    {SDLK_3, '3'},
-    {SDLK_4, '4'},
-    {SDLK_5, '5'},
-    {SDLK_6, '6'},
-    {SDLK_7, '7'},
-    {SDLK_8, '8'},
-    {SDLK_9, '9'},
-    {SDLK_UNDERSCORE, '_'},
-    {SDLK_a, 'A'},
-    {SDLK_b, 'B'},
-    {SDLK_c, 'C'},
-    {SDLK_d, 'D'},
-    {SDLK_e, 'E'},
-    {SDLK_f, 'F'},
-    {SDLK_g, 'G'},
-    {SDLK_h, 'H'},
-    {SDLK_i, 'I'},
-    {SDLK_j, 'J'},
-    {SDLK_k, 'K'},
-    {SDLK_l, 'L'},
-    {SDLK_m, 'M'},
-    {SDLK_n, 'N'},
-    {SDLK_o, 'O'},
-    {SDLK_p, 'P'},
-    {SDLK_q, 'Q'},
-    {SDLK_r, 'R'},
-    {SDLK_s, 'S'},
-    {SDLK_t, 'T'},
-    {SDLK_u, 'U'},
-    {SDLK_v, 'V'},
-    {SDLK_w, 'W'},
-    {SDLK_x, 'X'},
-    {SDLK_y, 'Y'},
-    {SDLK_z, 'Z'},
-    {SDLK_COMMA,','},
-    {0}
-};
-
-static byte get_keychar (int keysym) {
-    int i = 0;
-    while (keychar[i].keysym) {
-        if (keychar[i].keysym == keysym) return keychar[i].ch;
-        i++;
-    }
-    return 0;
-}
-
-static void shot (void) {
-/*
-  static int num=1;
-  char fn[100];//...
-#ifndef WIN32
-  char *e = getenv("HOME");
-  strncpy(fn, e, 60);
-  sprintf(&fn[strlen(fn)],"/.doom2d-rembo",num);
-  mkdir(fn, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
-  sprintf(&fn[strlen(fn)],"/shot%04d.bmp",num);
-#else
-  sprintf(fn,"shot%04d.bmp",num);
-#endif
-  SDL_SaveBMP(screen, fn);
-  ++num;
-*/
+static int state_for_anykey (int x) {
+  return x == GS_TITLE || x == GS_ENDSCR;
 }
 
 int GM_act (void) {
-  byte c;
-
-  if(mnu==&plcolor_mnu) {
-    if(*(++panimp)==0) panimp=panim;
-    GM_set(mnu);
-  }
-  if(movsndt>0) --movsndt; else movsndt=0;
-  if(g_st==GS_TITLE) if(!mnu) if(lastkey) {
-    GM_set(&main_mnu);Z_sound(msnd3,128);
-    lastkey=0;
-    return 1;
-  }
-  if(input) switch(lastkey) {
-    case SDLK_RETURN: case SDLK_KP_ENTER://case 0x1C: case 0x9C:
-      F_savegame(save_mnu.cur,ibuf);
-      input=0;GM_set(NULL);break;
-    case 1: input=0;GM_set(mnu);break;
-    case SDLK_BACKSPACE://case 0x0E:
-        if(icur) {ibuf[--icur]=0;GM_set(mnu);} break;
-    default:
-      if(icur>=23) break;
-      c=get_keychar(lastkey);//c=keychar[(keys[0x2A] || keys[0x36])?1:0][lastkey];
-      if(!c) break;
-      ibuf[icur]=c;ibuf[++icur]=0;GM_set(mnu);
-  }else {
-      switch(lastkey) {
-    case SDLK_ESCAPE://case 1:
-      if(!mnu) {GM_set(&main_mnu);Z_sound(msnd3,128);}
-      else {GM_set(NULL);Z_sound(msnd4,128);}
-      break;
-    case SDLK_F5:
-      if(mnu) break;
-      Z_sound(msnd3,128);
-      GMV_say("_GAMMA");
-      GM_set(&gamma_mnu);break;
-    case SDLK_F4://case 0x3E:
-      if(mnu) break;
-      Z_sound(msnd3,128);
-      GMV_say("_VOLUME");
-      GM_set(&vol_mnu);break;
-    case SDLK_F2://case 0x3C:
-      if(mnu) break;
-      if(g_st!=GS_GAME) break;
-      Z_sound(msnd3,128);
-      F_getsavnames();GM_set(&save_mnu);break;
-    case SDLK_F3://case 0x3D:
-      if(mnu) break;
-      Z_sound(msnd3,128);
-      F_getsavnames();GM_set(&load_mnu);break;
-    case SDLK_F10://case 0x44:
-      if(mnu) break;
-      Z_sound(msnd3,128);
-      GM_command(QUITGAME);break;
-    case SDLK_UP: case SDLK_KP8://case 0x48: case 0xC8:
-      if(!mnu) break;
-      if(mnu->type!=MENU) break;
-      if(--mnu->cur<0) mnu->cur=mnu->n-1;
-      GM_set(mnu);
-      Z_sound(msnd1,128);break;
-    case SDLK_DOWN: case SDLK_KP5: case SDLK_KP2://case 0x50: case 0xD0: case 0x4C:
-      if(!mnu) break;
-      if(mnu->type!=MENU) break;
-      if(++mnu->cur>=mnu->n) mnu->cur=0;
-      GM_set(mnu);
-      Z_sound(msnd1,128);break;
-    case SDLK_LEFT: case SDLK_RIGHT: case SDLK_KP4: case SDLK_KP6://case 0x4B: case 0x4D: case 0xCB: case 0xCD:
-         if(!mnu) break;
-         if(mnu->type!=MENU) break;
-         if(mnu->t[mnu->cur]<SVOLM) break;
-         GM_command(mnu->t[mnu->cur]+((lastkey==SDLK_LEFT || lastkey==SDLK_KP4)?0:1));//GM_command(mnu->t[mnu->cur]+((lastkey==0x4B || lastkey==0xCB)?0:1));
-         GM_set(mnu);
-         if(!movsndt) movsndt=Z_sound((lastkey==SDLK_LEFT || lastkey==SDLK_KP4)?msnd5:msnd6,255);//if(!movsndt) movsndt=Z_sound((lastkey==0x4B || lastkey==0xCB)?msnd5:msnd6,255);
-         break;
-    case SDLK_RETURN: case SDLK_SPACE: case SDLK_KP_ENTER://case 0x1C: case 0x39: case 0x9C:
-         if(!mnu) break;
-         if(mnu->type!=MENU) break;
-         if(mnu->t[mnu->cur]>=PL1CM) {
-           Z_sound(msnd2,128);
-           GM_command(PLCEND);
-           break;
-         }
-         if(mnu->t[mnu->cur]>=SVOLM) break;
-         Z_sound(msnd2,128);
-         GM_command(mnu->t[mnu->cur]);
-      break;
-    case SDLK_y://case 0x15:
-      if(!mnu) break;
-      if(mnu->type!=MSG) break;
-      Z_sound(msnd3,128);
-      GM_command(mnu->t[0]);
-      break;
-    case SDLK_n://case 0x31:
-      if(!mnu) break;
-      if(mnu->type!=MSG) break;
-      Z_sound(msnd4,128);
-      GM_command(mnu->t[1]);
-      break;
-    case SDLK_F1://case 0x3B:
-      if(shot_vga) {shot();Z_sound(msnd4,128);}
-      break;
+  menu_msg_t msg;
+  int n, cur, type;
+  const menu_t *m = GM_get();
+  if (m == NULL) {
+    if (lastkey == KEY_ESCAPE || (state_for_anykey(g_st) && lastkey != KEY_UNKNOWN)) {
+      GM_push(&main_menu);
+      Z_sound(msnd3, 128);
+    }
+  } else {
+    msg.type = GM_QUERY;
+    if (GM_send_this(m, &msg)) {
+      cur = msg.integer.i;
+      n = msg.integer.a;
+      msg.type = GM_GETENTRY;
+      if (GM_send(m, cur, &msg)) {
+        type = msg.integer.i;
+        switch (lastkey) {
+          case KEY_ESCAPE:
+            if (type == GM_TEXTFIELD && input) {
+              input = 0;
+              Y_disable_text_input();
+              msg.type = GM_CANCEL;
+              GM_send(m, cur, &msg);
+            } else {
+              GM_pop();
+              Z_sound(msnd4, 128);
+            }
+            break;
+          case KEY_UP:
+          case KEY_DOWN:
+            if (input == 0) {
+              msg.type = lastkey == KEY_UP ? GM_UP : GM_DOWN;
+              if (GM_send(m, cur, &msg)) {
+                Z_sound(msnd1, 128);
+              }
+            }
+            break;
+          case KEY_LEFT:
+          case KEY_RIGHT:
+            if (type == GM_SCROLLER) {
+              msg.integer.type = GM_GETINT;
+              if (GM_send(m, cur, &msg)) {
+                msg.integer.type = GM_SETINT;
+                msg.integer.i += lastkey == KEY_LEFT ? -msg.integer.s : msg.integer.s;
+                msg.integer.i = min(max(msg.integer.i, msg.integer.a), msg.integer.b);
+                if (GM_send(m, cur, &msg)) {
+                  Z_sound(lastkey == KEY_LEFT ? msnd5 : msnd6, 255);
+                }
+              }
+            } else if (type == GM_TEXTFIELD) {
+              //if (input) {
+              //  icur += lastkey == KEY_LEFT ? -1 : +1;
+              //  icur = min(max(icur, 0), strnlen(ibuf, imax));
+              //}
+            }
+            break;
+          case KEY_BACKSPACE:
+            if (type == GM_TEXTFIELD) {
+              if (input && icur > 0) {
+                // FIXIT buffers in strncpy must not overlap
+                strncpy(&ibuf[icur - 1], &ibuf[icur], imax - icur);
+                ibuf[imax - 1] = 0;
+                icur -= 1;
+              }
+            }
+            break;
+          case KEY_RETURN:
+            if (type == GM_TEXTFIELD) {
+              if (input) {
+                input = 0;
+                Y_disable_text_input();
+                msg.type = GM_END;
+                msg.string.s = ibuf;
+                msg.string.maxlen = imax;
+                GM_send(m, cur, &msg);
+              } else {
+                msg.type = GM_GETSTR;
+                if (GM_send(m, cur, &msg)) {
+                  imax = min(msg.string.maxlen, GM_MAX_INPUT);
+                  strncpy(ibuf, msg.string.s, imax);
+                  icur = strnlen(ibuf, imax);
+                } else {
+                  memset(ibuf, 0, GM_MAX_INPUT);
+                  imax = GM_MAX_INPUT;
+                  icur = 0;
+                }
+                input = 1;
+                Y_enable_text_input();
+                msg.type = GM_BEGIN;
+                GM_send(m, cur, &msg);
+              }
+              Z_sound(msnd2, 128);
+            } else {
+              msg.type = GM_SELECT;
+              if (cur < 0) abort();
+              if (GM_send(m, cur, &msg)) {
+                Z_sound(msnd2, 128);
+              }
+            }
+            break;
+        }
+      }
+    }
   }
+  lastkey = KEY_UNKNOWN;
+  return m != NULL;
+}
+
+void GM_input (int ch) {
+  if (ch != 0 && input) {
+    if (icur < imax) {
+      ibuf[icur] = ch;
+      icur += 1;
+      if (icur < imax) {
+        ibuf[icur] = 0;
+      }
+    }
   }
-  lastkey=0;
-  return((mnu)?1:0);
 }
 
-static void G_keyf (int k, int press) {
+void GM_key (int key, int down) {
   int i;
-
-  lastkey=k;
-  if(!_2pl || cheat) {
-    for(i=0;i<31;++i) cbuf[i]=cbuf[i+1];
-    cbuf[31]=get_keychar(k);
+  if (down) {
+    lastkey = key;
+    if (!_2pl || cheat) {
+      for (i = 0; i < 31; i++) {
+        cbuf[i] = cbuf[i + 1];
+      }
+      if (key >= KEY_0 && key <= KEY_9) {
+        cbuf[31] = key - KEY_0 + '0';
+      } else if (key >= KEY_A && key <= KEY_Z) {
+        cbuf[31] = key - KEY_A + 'A';
+      } else {
+        cbuf[31] = 0;
+      }
+    }
   }
 }
 
 void GM_init (void) {
-#ifndef DEMO
   int i;
-  static char nm[QSND_NUM][6]={
-       "CYBSIT","KNTDTH","MNPAIN","PEPAIN","SLOP","MANSIT","BOSPN","VILACT",
-       "PLFALL","BGACT","BGDTH2","POPAIN","SGTATK","VILDTH"
-  };
   char s[8];
-
-  s[0]='D';s[1]='S';
-  for(i=0;i<QSND_NUM;++i) {
-    memcpy(s+2,nm[i],6);
-    qsnd[i]=F_getresid(s);
+  static const char nm[QSND_NUM][6] = {
+    "CYBSIT", "KNTDTH", "MNPAIN", "PEPAIN", "SLOP", "MANSIT", "BOSPN", "VILACT",
+    "PLFALL", "BGACT", "BGDTH2", "POPAIN", "SGTATK", "VILDTH"
+  };
+  s[0] = 'D';
+  s[1] = 'S';
+  for (i = 0; i < QSND_NUM; ++i) {
+    memcpy(s + 2, nm[i], 6);
+    qsnd[i] = F_getresid(s);
   }
-#endif
-  csnd1=Z_getsnd("HAHA1");
-  csnd2=Z_getsnd("RADIO");
-  msnd1=Z_getsnd("PSTOP");
-  msnd2=Z_getsnd("PISTOL");
-  msnd3=Z_getsnd("SWTCHN");
-  msnd4=Z_getsnd("SWTCHX");
-  msnd5=Z_getsnd("SUDI");
-  msnd6=Z_getsnd("TUDI");
-  K_setkeyproc(G_keyf);
+  csnd1 = Z_getsnd("HAHA1");
+  csnd2 = Z_getsnd("RADIO");
+  msnd1 = Z_getsnd("PSTOP");
+  msnd2 = Z_getsnd("PISTOL");
+  msnd3 = Z_getsnd("SWTCHN");
+  msnd4 = Z_getsnd("SWTCHX");
+  msnd5 = Z_getsnd("SUDI");
+  msnd6 = Z_getsnd("TUDI");
+  MUS_load("MENU");
+  MUS_start(0);
 }