Browsing qwx/wl3d at commit HEAD: /hub.c

view raw
#include <u.h>
#include <libc.h>
#include <ctype.h>
#include <thread.h>
#include <keyboard.h>
#include "dat.h"
#include "fns.h"

extern Channel *csc;

int qtc;
Score scs[] = {
	{"id software-'92", 10000, 1},
	{"Adrian Carmack", 10000, 1},
	{"John Carmack", 10000, 1},
	{"Kevin Cloud", 10000, 1},
	{"Tom Hall", 10000, 1},
	{"John Romero", 10000, 1},
	{"Jay Wilbur", 10000, 1},
};
char savs[11][32];

typedef struct Fade Fade;
typedef struct Sp Sp;
typedef struct Seq Seq;
typedef struct Item Item;
typedef struct Menu Menu;

enum{
	LMctl,
	LMnew,
	LMdifc,
	LMsnd,
	LMin,
	LMsav,
	LMmsg,

	Lload = 0,
	Lintro,
	Lftitle,
	Ltitle,
	Lcreds,
	Lscore,
	Lfpants,
	Lpants,
	Ldemo,
	Lpsych,
	Lgame,
	Lretry,
	Lmsg,
	Lcont1,
	Lcont2,
	Lgcont,
	Lfdie,
	Ldie,
	Ldie2,
	Lhigh,
	Lcam,
	Lspear,
	Linter,
	Linteri,
	Linterm,
	Linterw,
	Lintere,
	Lsinter,
	Lsdmend,
	Lcolp,
	Lcolp2,
	Lcolp3,
	Lwon,
	Lwon2,
	Lsdepi,
	Lpres,
	Lroll1,
	Lroll2,
	Lroll3,
	Lroll4,
	Lroll5,
	Lroll6,
	Lroll7,
	Ldecay,
	Lctl,
	Ltoctl,
	Lftoctl,
	Lmtoctl,
	Lcur,
	Lftonew,
	Lmtonew,
	Lnewgame,
	Lnctl,
	Ldenied,
	Ldifc1,
	Ldifc2,
	Ldifc3,
	Ldifc4,
	Ldifc5,
	Lfsnd,
	Lmsnd,
	Lsctl,
	Lsndtog,
	Lfin,
	Lmin,
	Lictl,
	Lintog,
	Lfsens,
	Lmsens,
	Lsectl,
	Lfsav,
	Lmsav,
	Lsvctl,
	Lsvname,
	Lwrsav,
	Lflod,
	Lmlod,
	Lldctl,
	Lldsav,
	Lldsav2,
	Lldsav3,
	Lldsav4,
	Lldsav5,
	Lfvw,
	Lmvw,
	Lvwctl,
	Lfmscore,
	Lmscore,
	Lovrsav,
	Lend,
	Lcurgame,
	Lquit,
	Lmexit,
	Lexit
};
struct Sp{
	int dt;
	void (*f)(void);
};
struct Fade{
	Col c;
	int dt;
};
struct Seq{
	void (*init)(void);
	Sp *s;
	Sp *e;
	Seq *q;
	Fade *f;
};
static Seq *qsp, *qesc, ql[];
static Sp *qp;

enum{
	DMbg,
	DMoff,
	DMbrd,
	DMbrd2,
	DMend,

	DIreg = 0x17,
	DIsee = 0x4a,
	DIeps = 0x6b,
	DIrhi = 0x13,
	DIshi = 0x47,
	DIepi = 0xd0,
};
static int mcol[DMend] = {[DMbg] 0x2d, 0};

#define SEL(c)		((c) - 4 + (((c) >> 1 ^ (c)) >> 5 & 1))
#define UNSEL(c)	((c) + 4 - (((c) >> 1 ^ (c)) >> 5 & 1))

struct Item{
	char *s;
	int c;
	Seq *q;
	int a;
};
static Item
ictl[] = {
	{"New Game", SEL(DIreg), ql+Lftonew},
	{"Sound", DIreg, ql+Lfsnd},
	{"Control", DIreg, ql+Lfin},
	{"Load Game", DIreg, ql+Lflod},
	{"Save Game", DIreg, ql+Lfsav},
	{"Change View", DIreg, ql+Lfvw},
	{"View Scores", DIreg, ql+Lfmscore},
	{"Back to Demo", DIreg, ql+Lftitle},
	{"Quit", DIreg, ql+Lquit}
},
inew[] = {
	{"Episode 1\nEscape from Wolfenstein", SEL(DIreg), ql+Ldifc1},
	{nil},
	{"Episode 2\nOperation: Eisenfaust", DIreg, ql+Ldifc1},
	{nil},
	{"Episode 3\nDie, Fuhrer, Die!", DIreg, ql+Ldifc1},
	{nil},
	{"Episode 4\nA Dark Secret", DIreg, ql+Ldifc1},
	{nil},
	{"Episode 5\nTrail of the Madman", DIreg, ql+Ldifc1},
	{nil},
	{"Episode 6\nConfrontation", DIreg, ql+Ldifc1},
	{nil}
},
idifc[] = {
	{"Can I play, Daddy?", DIreg, ql+Ldifc4},
	{"Don't hurt me.", DIreg, ql+Ldifc4},
	{"Bring 'em on!", SEL(DIreg), ql+Ldifc4},
	{"I am Death incarnate!", DIreg, ql+Ldifc4}
},
isnd[] = {
	{"None", SEL(DIreg)},
	{"PC Speaker"},
	{"AdLib/Sound Blaster", DIreg},
	{nil},
	{nil},
	{"None", DIreg},
	{"Disney Sound Source"},
	{"Sound Blaster", DIreg},
	{nil},
	{nil},
	{"None", DIreg},
	{"AdLib/Sound Blaster", DIreg}
},
iin[] = {
	{"Mouse Enabled", SEL(DIreg), ql+Lintog},
	{"Autorun Enabled", DIreg, ql+Lintog},
	{"Mouse Sensitivity", DIreg, ql+Lfsens}
},
isv[] = {
	{"", SEL(DIreg), nil},
	{"", DIreg, nil},
	{"", DIreg, nil},
	{"", DIreg, nil},
	{"", DIreg, nil},
	{"", DIreg, nil},
	{"", DIreg, nil},
	{"", DIreg, nil},
	{"", DIreg, nil},
	{"", DIreg, nil}
},
imsg[] = {
	{nil}
};
struct Menu{
	Item *p;
	Item *s;
	Item *e;
	int x;
	int y;
	int n;
};
static Menu *mp, ml[] = {
	[LMctl] {ictl, ictl, ictl+nelem(ictl), 72},
	[LMnew] {inew, inew, inew+nelem(inew), 8},
	[LMdifc] {idifc+2, idifc, idifc+nelem(idifc), 48},
	[LMsnd] {isnd, isnd, isnd+nelem(isnd), 48},
	[LMin] {iin, iin, iin+nelem(iin), 24},
	[LMsav] {isv, isv, isv+nelem(isv), 80},
	[LMmsg] {imsg}
};

static char *ends[] = {
	"Dost thou wish to\nleave with such hasty\nabandon?",
	"Chickening out...\nalready?",
	"Press N for more carnage.\nPress Y to be a weenie.",
	"So, you think you can\nquit this easily, huh?",
	"Press N to save the world.\nPress Y to abandon it in\nits hour of need.",
	"Press N if you are brave.\nPress Y to cower in shame.",
	"Heroes, press N.\nWimps, press Y.",
	"You are at an intersection.\nA sign says, 'Press Y to quit.'\n>",
	"For guns and glory, press N.\nFor work and worry, press Y.",

	"Heroes don't quit, but\ngo ahead and press Y\nif you aren't one.",
	"Press Y to quit,\nor press N to enjoy\nmore violent diversion.",
	"Depressing the Y key means\nyou must return to the\nhumdrum workday world.",
	"Hey, quit or play,\nY or N:\nit's your choice.",
	"Sure you don't want to\nwaste a few more\nproductive hours?",
	"I think you had better\nplay some more. Please\npress N...please?",
	"If you are tough, press N.\nIf not, press Y daintily.",
	"I'm thinkin' that\nyou might wanna press N\nto play more. You do it.",
	"Sure. Fine. Quit.\nSee if we care.\nGet it over with.\nPress Y."
};
static char **quits;

static Fade
	fblk = {{0, 0, 0}, 30},
	ftra = {{0, 0, 0}, 10},
	fctl = {{0xae, 0, 0}, 10},
	focl = {{0, 0x44, 0x44}, 300},
	ficl = {{0, 0x44, 0x44}, 30},
	fecl = {{0, 0x44, 0x44}, 5};

static int irr[4], *irp;
static int iri, iry, irb;

static uchar **demd;
static char *demf;

static void
reset(Seq *p)
{
	qp = p->s;
	qtc = 0;
	if(p == qsp)
		return;
	toss();
	if(qp->f != fadeout)
		pal = pals[C0];
	qsp = p;
	if(p->init != nil)
		p->init();
	if(p->f != nil)
		fadeop(&p->f->c, p->f->dt);
}

static void
pblink(void)
{
	if(mp->n ^= 1)
		put(mp->x, mp->y, fnt->w['_'], fnt->h, DIreg);
	else
		txt(mp->x, mp->y, "_", 0);
	out();
}

static void
iblink(void)
{
	if(mp->n ^= 1)
		txt(111 + irb - 1, mp->y + 3, "\x80", DIrhi);
	else
		put(111 + irb - 1, mp->y + 3, fnt->w[0x80]-1, fnt->h, mcol[DMbg]);
	out();
}

static void
blink(void)
{
	put(mp->x, mp->y, 24, 16, mcol[DMbg]);
	pic(mp->x, mp->y, pict[Pcur1] + mp->n);
	mp->n ^= 1;
	out();
}

static void
iswp(Item *on, Item *off)
{
	on->a = 1;
	on->q = nil;
	off->a = 0;
	off->q = ql+Lsndtog;
}

static void
toggle(void)
{
	Item *i;

	i = mp->s;
	switch(mp - ml){
	case LMsnd:
		if(mp->p->a)
			return;
		switch(mp->p - i){
		case 0: iswp(i, i+2); stopsfx(); sfxon = 0; break;
		case 2: iswp(i+2, i); sfxon = 1; sfx(Sshoot); break;
		case 5: iswp(i+5, i+7); pcmon = 0; break;
		case 7: iswp(i+7, i+5); pcmon = 1; break;
		case 10: iswp(i+10, i+11); stopmus(); muson = 0; sfx(Sshoot); break;
		case 11: iswp(i+11, i+10); muson = 1; mus(Mmenu); break;
		}
		break;
	case LMin:
		switch(mp->p - i){
		tog: mp->p->a ^= 1; break;
		case 0: grabon ^= 1; goto tog;
		case 1: autorun ^= 1; goto tog;
		}
		break;
	}
}

static void
inctl(void)
{
	Item *i, *e;
	Menu *m;

	m = ml+LMctl;
	i = m->s;
	i[3].c = DIreg;
	i[4].c = mcol[DMoff];
	i[6].s = "View Scores";
	i[6].q = ql+Lfmscore;
	i[6].c = DIreg;
	i[7].s = "Back to Demo";
	i[7].q = ql+Lftitle;
	i[7].c = DIreg;
	m->p = m->s;
	m->p->c = SEL(DIreg);
	if(ver >= SDM)
		i[0].q = ql+Ldifc1;
	else
		for(i=ml[LMnew].s, e=ml[LMnew].e; i<e && i->q!=ql+Ldenied; i++)
			i->q = ql+Ldifc1;
}

static void
ingctl(void)
{
	Item *i, *e;

	i = ml[LMctl].s;
	i[4].c = DIreg;
	i[6].s = "End Game";
	i[6].q = ql+Lend;
	i[7].s = "Back to Game";
	i[7].q = ql+Lcont1;
	i[7].c = DIsee;
	if(i[0].q == ql+Ldifc1)
		i[0].q = ql+Lcurgame;
	else
		for(i=ml[LMnew].s, e=ml[LMnew].e; i<e && i->q!=ql+Ldenied; i++)
			i->q = ql+Lcurgame;
}

static void
mbox(int x, int y, int dx, int dy)
{
	box(x, y, dx, dy, mcol[DMbg], mcol[DMbrd2], mcol[DMoff]);
}

static void
msg(char *s, int o)
{
	int x, y, w, h, curw;
	char *nl;

	fnt = fnts+1;
	h = txth(s);
	w = txtw(s);
	nl = strrchr(s, '\n');
	curw = txtw(nl != nil ? nl + 1 : s);
	w = w > curw + 10 ? w : curw + 10;
	x = Vw / 2 - w / 2;
	y = (step == gstep ? Vhud : Vh) / 2 - h / 2;
	box(x - 5, y - 5, w + 10, h + 10, DIreg, 0, DIrhi);
	txtnl(x, y, s, 0);
	if(o)
		out();
	mp = ml+LMmsg;
	mp->n = 0;
	mp->x = x + curw;
	mp->y = y + h - fnt->h;
}

static void
quit(void)
{
	msg(quits[nrand(nelem(ends)/2)], 0);
	mp->p->q = ql+Lmexit;
	qesc = ql+Lctl;
}

static void
curgame(void)
{
	msg("You are currently in\na game. Continuing will\nerase old game. Ok?", 0);
	mp->p->q = ql+Ldifc1;
	qesc = ver < SDM ? ql+Lftoctl : ql+Lctl;
}

static void
mend2(void)
{
	view();
	render();
	gm.lives = 0;
	ql[Ldie].q = ql+Ldie2;
}

static void
mend(void)
{
	msg("Are you sure you want\nto end the game you\nare playing? (Y or N):", 0);
	mp->p->q = ql+Lfdie;
	qesc = ql+Lctl;
}

static void
ovrsav(void)
{
	msg("There's already a game\n"
		"saved at this position.\n      Overwrite?", 0);
	mp->p->q = ql+Lsvname;
	qesc = ql+Lsvctl;
}

static void
denied(void)
{
	msg("Please select \"Read This!\"\nfrom the Options menu to\n"
		"find out how to order this\nepisode from Apogee.", 1);
	stopsfx();
	sfx(Snoway);
}

static void
sdmend(void)
{
	sfx(S1up);
	msg("This concludes your demo\nof Spear of Destiny! Now,\n"
		"go to your local software\nstore and buy it!", 1);
}

static void
setdifc(void)
{
	int m, d;

	m = ver < SDM ? (ml[LMnew].p - ml[LMnew].s) * 5 : 0;
	d = ml[LMdifc].p - ml[LMdifc].s;
	ginit(nil, m, d);
	ingctl();
	palfill(&fctl.c);
}

static void
difc(void)
{
	Menu *m;
	Item *i, *e;

	mclear();
	pic(112, 184, pict[Pmouselback]);
	if(ver < SDM){
		txt(70, 68, "How tough are you?", DIshi);
		qesc = ql+Lftonew;
	}else{
		pic(70, 68, pict[Pdiffc]);
		qesc = ql+Lftoctl;
	}
	mbox(45, 90, 225, 67);
	m = ml+LMdifc;
	mp = m;
	i = m->s;
	e = m->e;
	do{
		txt(74, 100 + 13 * (i - m->s), i->s, i->c);
	}while(++i < e);
	pic(235, 107, pict[Pbaby] + m->p - m->s);
	m->n = 0;
	m->y = 98 + 13 * (m->p - m->s);
}

static void
newgame(void)
{
	int n;
	Item *i, *e;
	Menu *m;

	mclear();
	pic(112, 184, pict[Pmouselback]);
	mbox(6, 19, 308, 162);
	txtcen(2, "Which episode to play?", DIshi);
	for(n=0; n<6; n++)
		pic(40, 23 + n * 26, pict[Pep1] + n);
	m = ml+LMnew;
	mp = m;
	i = m->s;
	e = m->e;
	do{
		txtnl(98, 23 + 13 * (i - m->s), i->s, i->c);
		i += 2;
	}while(i < e);
	qesc = ql+Lftoctl;
	m->n = 0;
	m->y = 21 + 13 * (m->p - m->s);
}

static void
snd(void)
{
	Item *i, *e;
	Menu *m;

	mclear();
	pic(112, 184, pict[Pmouselback]);
	mbox(40, 17, 250, 45);
	mbox(40, 82, 250, 45);
	mbox(40, 147, 250, 32);
	pic(96, 0, pict[Psfx]);
	pic(96, 65, pict[Ppcm]);
	pic(96, 130, pict[Pmus]);
	m = ml+LMsnd;
	mp = m;
	i = m->s;
	e = m->e;
	do{
		if(i->s == nil)
			continue;
		txt(100, 20 + 13 * (i - m->s), i->s, i->c);
		pic(72, 22 + 13 * (i - m->s), pict[i->a ? Psel : Punsel]);
	}while(++i < e);
	qesc = ql+Lftoctl;
	m->n = 0;
	m->y = 18 + 13 * (m->p - m->s);
}

static void
sens(void)
{
	int n;

	mclear();
	pic(112, 184, pict[Pmouselback]);
	mbox(10, 80, 300, 30);
	txtcen(82, "Adjust Mouse Sensitivity", DIsee);
	txt(14, 95, "Slow", DIreg);
	txt(269, 95, "Fast", DIreg);
	put(60, 97, 200, 10, DIreg);
	outbox(60, 97, 200, 10, 0x0, DIrhi);
	n = 60 + 20 * iri;
	put(n + 1, 98, 19, 9, DIshi);
	outbox(n, 97, 20, 10, 0x0, DIsee);
	qesc = ql+Lfin;
}

static void
in(void)
{
	Menu *m;
	Item *i, *e;

	mclear();
	stripe(10);
	pic(80, 0, pict[Pctl]);
	pic(112, 184, pict[Pmouselback]);
	mbox(16, 65, 284, 45);
	m = ml+LMin;
	mp = m;
	i = m->s;
	e = m->e;
	do{
		txt(80, 70 + 13 * (i - m->s), i->s, i->c);
		if(i < e - 1)
			pic(56, 73 + 13 * (i - m->s), pict[i->a ? Psel : Punsel]);
	}while(++i < e);
	qesc = ql+Lftoctl;
	m->n = 0;
	m->y = 68 + 13 * (m->p - m->s);
	iri = msense;
}

static void
disk(void)
{
	int n;

	box(96, 80, 130, 42, DIreg, 0, DIrhi);
	pic(104, 85, pict[Pread1]);
	fnt = fnts+1;
	txt(142, 93, qsp == ql+Lwrsav ? "Saving..." : "Loading...", 0);
	out();
	n = mp->p - mp->s;
	if(qsp == ql+Lwrsav){
		qsp->q = ql+Lftoctl;
		wrsav(n);
	}else{
		qsp->q = ql+Lldsav2;
		ginit(nil, -1, 0);
		greset();
		ldsav(n);
		ingctl();
		loaded++;
	}
	sfx(Sshoot);
}

static void
savtxt(Item *i, char *s)
{
	int n;

	n = 56 + (i - mp->s) * 13;
	put(110, n, 135, fnt->h, mcol[DMbg]);
	txt(111, n, s, i->c);
}

static void
savitem(Item *i, int n)
{
	outbox(109, 55 + n * 13, 136, 11, i->c, i->c);
	if(savs[n][0] != 0){
		savtxt(i, savs[n]);
		i->q = qsp->q == ql+Lsvctl ? ql+Lovrsav : ql+Lldsav;
	}else{
		savtxt(i, "      - empty -");
		i->q = qsp->q == ql+Lsvctl ? ql+Lsvname : nil;
	}
}

static void
sav(void)
{
	Menu *m;
	Item *i, *e;

	mclear();
	pic(112, 184, pict[Pmouselback]);
	mbox(75, 50, 175, 140);
	stripe(10);
	pic(56, 0, pict[qsp->q == ql+Lldctl ? Pload : Psave]);
	m = ml+LMsav;
	mp = m;
	i = m->s;
	e = m->e;
	fnt = fnts;
	do
		savitem(i, i - m->s);
	while(++i < e);
	qesc = ql+Lftoctl;
	m->n = 0;
	m->y = 53 + 13 * (m->p - m->s);
}

static void
savname(void)
{
	sav();
	memcpy(savs[10], savs[mp->p - mp->s], sizeof savs[10]);
	savtxt(mp->p, savs[10]);
	iri = strlen(savs[10]);
	irb = txtw(savs[10]);
	mp->n = 0;
}

static void
mvw(void)
{
	int dx, dy;

	dx = vw.dx;
	dy = vw.dy;
	vw.dx = iri * 16;
	vw.dy = iri * 8;
	viewbox();
	vw.dx = dx;
	vw.dy = dy;
	put(0, Vhud, Vw, 40, 0x7f);
	txtcen(161, "Use arrows to size", DIrhi);
	txtcen(161 + fnt->h, "ENTER to accept", DIrhi);
	txtcen(161 + 2 * fnt->h, "ESC to cancel", DIrhi);
	qesc = ql+Lftoctl;
}

static void
ctl(void)
{
	Menu *m;
	Item *i, *e;

	mclear();
	pic(112, 184, pict[Pmouselback]);
	stripe(10);
	pic(80, 0, pict[Popt]);
	mbox(68, 52, 178, 123);
	fnt = fnts+1;
	m = ml+LMctl;
	mp = m;
	i = m->s;
	e = m->e;
	do
		txt(100, 55 + 13 * (i - m->s), i->s, i->c);
	while(++i < e);
	qesc = ql+Lquit;
	m->n = 0;
	m->y = 53 + 13 * (m->p - m->s);
	if(qsp == ql+Ltoctl)
		stopsfx();
	mus(Mmenu);
	grab(0);
	iri = vwsize;
}

static void
cursfx(void)
{
	sfx(Sdrawgun2);
}

static void
slcur(int dir)
{
	iri += dir;
	switch(qsp - ql){
	case Lsectl:
		if(iri < 0)
			iri = 0;
		else if(iri > 9)
			iri = 9;
		break;
	case Lvwctl:
		if(iri < 4)
			iri = 4;
		else if(iri > 19)
			iri = 19;
		break;
	}
	qsp->init();
	out();
}

static void
slider(void)
{
	Rune r;

	if(nbrecv(csc, &r) <= 0)
		return;
	switch(r){
	case Kleft: case Kdown: slcur(-1); break;
	case Kright: case Kup: slcur(1); break;
	case Kesc: sfx(Sesc); reset(qesc); break;
	case '\n':
	case ' ':
		switch(qsp - ql){
		case Lsectl: msense = iri; break;
		case Lvwctl: vwsize = iri; msg("Thinking...", 1); setvw(); break;
		}
		sfx(Sshoot);
		reset(qesc);
		break;
	}
}

static void
movcur(Item *p, int dir)
{
	Item *i;

	p->c = UNSEL(p->c);
	i = p;
	do{
		i += dir;
		if(i < mp->s)
			i = mp->e - 1;
		else if(i == mp->e)
			i = mp->s;
	}while(i->c == mcol[DMoff] || i->c == 0);
	i->c = SEL(i->c);
	mp->p = i;
	mp->n = 0;
	if(i != p + dir){
		cursfx();
		reset(qsp);
		if(qsp->init != nil)
			qsp->init();
		return;
	}
	put(mp->x, mp->y, 24, 16, mcol[DMbg]);
	mp->y += dir * 6;
	blink();
	sfx(Sdrawgun1);
	ql[Lcur].q = qsp;
	reset(ql+Lcur);
}

static void
cwalk(void)
{
	static char s[UTFmax*2], *sp = s;
	Rune r;
	Seq *q;
	Item *i;

	if(nbrecv(csc, &r) <= 0)
		return;
	i = mp->p;
	switch(r){
	case Kup: movcur(i, -1); break;
	case Kdown: movcur(i, 1); break;
	case Kesc: sfx(Sesc); reset(qesc); break;
	case '\n':
	case ' ':
		q = i->q;
		if(q == nil)
			break;
		if(q != ql+Lquit && q != ql+Lftitle)
			sfx(Sshoot);
		reset(q);
		break;
	default:
		if(ver < SOD)
			break;
		sp += runetochar(sp, &r);
		if(strncmp(s, "id", sp-s) == 0){
			if(sp - s == 2){
				reset(ql+Lfpants);
				sp = s;
			}
		}else
			sp = s;
	}
}

static void
prompt(void)
{
	int n, m;
	char *s;
	Rune r;

	if(nbrecv(csc, &r) <= 0)
		return;
	s = savs[10];
	n = strlen(s);
	switch(r){
	redraw:
		savtxt(mp->p, savs[10]);
		mp->n = 0;
		iblink();
		qtc = 0;
		break;
	default:
		if(runelen(r) > 1 || !isprint(r))
			break;
		m = fnt->w[r];
		if(txtw(s) + m > 134 || n == sizeof(savs[10]) - 1)
			break;
		while(n-- > iri)
			s[n+1] = s[n];
		s[iri++] = r;
		irb += m;
		goto redraw;
	case Kleft:
		if(iri == 0)
			break;
		irb -= fnt->w[s[--iri]];
		goto redraw;
	case Kright:
		if(iri == n)
			break;
		irb += fnt->w[s[iri++]];
		goto redraw;
	case Kdel:
	case Kbs:
		if(iri == 0)
			break;
		irb -= fnt->w[s[--iri]];	
		for(m=iri+1; m<=n; m++)
			s[m-1] = s[m];
		s[m] = 0;
		goto redraw;
	abort:
	case Kesc:
		sfx(Sesc);
		reset(ql+Lsvctl);
		break;
	case '\n':
		if(n == 0)
			goto abort;
		strcpy(savs[mp->p - mp->s], savs[10]);
		sfx(Sshoot);
		reset(ql+Lwrsav);
		break;
	}
}

static void
ask(void)
{
	Rune r;

	if(nbrecv(csc, &r) <= 0)
		return;
	if(r == 'y'){
		sfx(Sshoot);
		reset(mp->p->q);
	}else if(r == 'n' || r == Kesc){
		sfx(Sesc);
		reset(qesc);
	}
}

static void
skip(void)
{
	if(nbrecv(csc, nil) > 0)
		reset(ql+Ldecay);
}
static void
swait(void)
{
	if(lastsfx() >= 0)
		qtc = 0;
	else
		sfxlck = 0;
}
static void
nbwait(void)
{
	if(nbrecv(csc, nil) > 0)
		qtc = qp->dt;
}
static void
bwait(void)
{
	qtc = nbrecv(csc, nil) > 0 ? qp->dt : 0;
}

static void
pants(void)
{
	pic(0, 0, pict[Pid1]);
	pic(0, 80, pict[Pid2]);
	palpic(exts[Eid]);
	mus(Mnazjazz);
}

static void
roll(void)
{
	pic(0, 0, pict[Pend1]+iri);
	palpic(exts[Eend1+iri]);
	iri++;
	if(iri == 9)
		iri = 1;
}
static void
pres3(void)
{
	put(0, 180, Vw, 20, 0);
	txtcen(180, "With the spear gone, the Allies will finally", DIepi);
	/* bug: typo "by able to" */
	txtcen(180 + fnt->h, "be able to destroy Hitler...", DIepi);
	out();
	iri = 3;
}
static void
pres2(void)
{
	txtcen(180, "We owe you a great debt, Mr. Blazkowicz.", DIepi);
	txtcen(180 + fnt->h, "You have served your country well.", DIepi);
	out();
}
static void
pres1(void)
{
	pic(0, 0, pict[Pend1]+2);
	palpic(exts[Eend1+2]);
	fnt = fnts;
}
static void
sdepi(void)
{
	pic(0, 0, pict[Pend1]);
	palpic(exts[Eend1]);
}

static void
won(void)
{
	int n;
	char a[10];

	put(0, 0, Vw, Vhud, 0x7f);
	pic(8, 4, pict[Pwin]);
	pictxt(144, 16, "YOU WIN!");
	pictxt(112, 48, "TOTAL TIME");
	if(gm.eptm > 99 * 60)
		gm.eptm = 99 * 60 + 99;
	sprint(a, "%02d:%02d", gm.eptm / 60, gm.eptm % 60);
	pictxt(113, 64, a);
	pictxt(96, 96, "AVERAGES");
	n = ver < SDM ? 8 : 14;
	sprint(a, "KILL %3d%%", (gm.epk / n));
	pictxt(112, 112, a);
	sprint(a, "SECRET %3d%%", (gm.eps / n));
	pictxt(80, 128, a);
	sprint(a, "TREASURE %3d%%", (gm.ept / n));
	pictxt(48, 144, a);
	if(ver == WL6 && gm.difc >= GDmed){
		pic(240, 64, pict[Ptc]);
		fnt = fnts;
		n = gm.eptm / 60;
		a[0] = 'A' + (n / 10 ^ n % 10 ^ 0xa);
		n = gm.eptm % 60;
		a[1] = 'A' + (n / 10 ^ n % 10 ^ 0xa);
		a[2] = 'A' + (a[0] ^ a[1]);
		a[3] = 0;
		txt(241, 72, a, DIshi);
	}
	mus(ver < SDM ? Mwon : Msdwon);
	grab(0);
}

static void
colp(void)
{
	pic(124, 44, pict[Pcollapse] + iri++);
	out();
}
static void
incolp(void)
{
	iri = 1;
	fill(0x7f);
	pic(124, 44, pict[Pcollapse]);
	mus(Mend);
}

static void
breathe(void)
{
	static int n, t;

	if(++t > 35){
		t = 0;
		n ^= 1;
		pic(0, 16, pict[n ? Pguy2 : Pguy]);
		out();
	}
}

static void
sinterw(void)
{
	breathe();
	if(nbrecv(csc, nil) <= 0)
		qtc = 0;
}

static void
siscore(void)
{
	givep(15000);
	gm.oldpt = gm.pt;
}
static void
sinter(void)
{
	put(0, 0, Vw, Vhud, 0x7f);
	pic(0, 16, pict[Pguy]);
	if(ver < SDM)
		pictxt(112, 32, "SECRET FLOOR\n COMPLETED!");
	else{
		switch(gm.map){
		case 4: pictxt(112, 32, " TRANS\n GROSSE\nDEFEATED!"); break;
		case 9: pictxt(112, 32, "BARNACLE\nWILHELM\nDEFEATED!"); break;
		case 15: pictxt(112, 32, "UBERMUTANT\nDEFEATED!"); break;
		case 17: pictxt(112, 32, " DEATH\n KNIGHT\nDEFEATED!"); break;
		case 18: pictxt(104, 32, "SECRET TUNNEL\n    AREA\n  COMPLETED!"); break;
		case 19: pictxt(104, 32, "SECRET CASTLE\n    AREA\n  COMPLETED!"); break;
		}
	}
	if(gm.map == 17){	/* solely for crmskp */
		gm.com = GMup;
		gm.map = 19;
	}
	pictxt(80, 128, "15000 BONUS!");
	mus(Minter);
}

static void
interw(void)
{
	breathe();
	if(nbrecv(csc, nil) > 0)
		reset(ver == SDM && gm.map == 1 ? ql+Lsdmend : ql+Lintere);
}

static void
intere(void)
{
	givep(irb);
	gm.oldpt = gm.pt;
	reset(ql+Linterw);
}

static void
interskip(void)
{
	int n;
	char a[10];

	irb = irr[0] * 500;
	irb += 10000 * ((irr[1] == 100) + (irr[2] == 100) + (irr[3] == 100));
	n = sprint(a, "%d", irb);
	pictxt((36 - n * 2) * 8, 56, a);
	n = sprint(a, "%d", irr[1]);
	pictxt((37 - n * 2) * 8, 112, a);
	n = sprint(a, "%d", irr[2]);
	pictxt((37 - n * 2) * 8, 128, a);
	n = sprint(a, "%d", irr[3]);
	pictxt((37 - n * 2) * 8, 144, a);
	intere();
}

static void
interb(void)
{
	if(nbrecv(csc, nil) > 0)
		interskip();
	else if(lastsfx() >= 0){
		breathe();
		qtc = 0;
	}
}

static void
interm(void)
{
	int n;
	char a[10];

	if(nbrecv(csc, nil) > 0){
		interskip();
		return;
	}
	if(*irp == 100){
		stopsfx();
		irb += 10000;
		n = sprint(a, "%d", irb);
		pictxt((36 - n * 2) * 8, 56, a);
		out();
		sfx(S100);
	}else if(*irp == 0)
		sfx(Snobonus);
	else
		sfx(Sendb2);
	iri = 0;
	qsp->q = ql+Linteri;
	qsp->q->q = ql+Linteri;
	switch(++irp - irr){
	case 1: iry = 112; break;
	case 2: iry = 128; break;
	case 3: iry = 144; break;
	case 4: intere(); break;
	}
}

static void
interi(void)
{
	int n;
	char a[10];

	if(nbrecv(csc, nil) > 0){
		interskip();
		return;
	}
	n = sprint(a, "%d", iri);
	pictxt((37 - n * 2) * 8, iry, a);
	sfx(Sendb1);
	out();
	iri += 10;
	if(iri == *irp + 10){
		ql[Linterm].s[0].dt = *irp == 100 || *irp == 0 ? 30 : 0;
		qsp->q = ql+Linterm;
	}else if(iri >= *irp)
		iri = *irp;
}

static void
interp(void)
{
	int m;
	char a[10];

	m = sprint(a, "%d", iri * 500);
	pictxt((18 - m) * 16, 56, a);
	sfx(Sendb1);
	out();
	iri += 50;
	if(iri == *irp + 50){
		sfx(Sendb2);
		iri = 0;
		irb += iri * 500;
		iry = 112;
		irp++;
		qsp->s[0].f = interi;
	}else if(iri >= *irp)
		iri = *irp;
}

static void
inter(void)
{
	static int wlpar[] = {
		90, 120, 120, 210, 180, 180, 150, 150, 0, 0,
		90, 210, 180, 120, 240, 360, 60, 180, 0, 0,
		90, 90, 150, 150, 210, 150, 120, 360, 0, 0,
		120, 120, 90, 60, 270, 210, 120, 270, 0, 0,
		150, 90, 150, 150, 240, 180, 270, 210, 0, 0,
		390, 240, 270, 360, 300, 330, 330, 510, 0, 0
	},
	sdpar[] = {
		 90, 210, 165, 210, 0, 270, 195, 165, 285, 0,
		 390, 270, 165, 270, 360, 0, 360, 0, 0, 0
	};
	int s, p;
	char a[10];

	put(0, 0, Vw, Vhud, 0x7f);
	pic(0, 16, pict[Pguy]);
	pictxt(112, 16, "FLOOR\nCOMPLETED");
	sprint(a, "%d", ver<SDM ? gm.map % 10 + 1 : gm.map + 1);
	pictxt(208, 16, a);
	pictxt(112, 56, "BONUS     0");
	pictxt(128, 80, "TIME");
	pictxt(128, 96, " PAR");
	p = ver < SDM ? wlpar[gm.map] : sdpar[gm.map];
	sprint(a, "%02d:%2d", p / 60, p % 60);
	pictxt(208, 96, a);
	pictxt(72, 112, "KILL RATIO    %");
	pictxt(40, 128, "SECRET RATIO    %");
	pictxt(8, 144, "TREASURE RATIO    %");
	s = gm.tc / Tb;
	if(s > 99 * 60)
		s = 99 * 60;
	sprint(a, "%02d:%02d", s / 60, s % 60);
	pictxt(208, 80, a);
	gm.eptm += s;
	memset(irr, 0, sizeof irr);
	s = p - s;
	irr[0] = s > 0 ? s : 0;
	if(gm.ktot)
		irr[1] = gm.kp * 100 / gm.ktot;
	if(gm.stot)
		irr[2] = gm.sp * 100 / gm.stot;
	if(gm.ttot)
		irr[3] = gm.tp * 100 / gm.ttot;
	gm.epk += irr[1];
	gm.eps += irr[2];
	gm.ept += irr[3];
	irp = irr;
	iri = 0;
	irb = 0;
	if(irr[0] == 0){
		ql[Linteri].s[0].f = interi;
		iry = 112;
		irp++;
	}else
		ql[Linteri].s[0].f = interp;
	ql[Linteri].q = ql+Linteri;
	mus(Minter);
}

static void
gcont(void)
{
	kb = 0;
	mΔx = mΔy = 0;
	mb = 0;
	if(!gm.demo){
		kbon++;
		grab(1);
	}
	step = gstep;
}

static void
camtxt2(void)
{
	camwarp();
	qtc = 32;
	render();
	fizzop(-1, 1);
	put(0, 0, Vw, Vhud, 0x7f);
	viewbox();
}
static void
camtxt(void)
{
	pictxt(0, 56, "LET\'S SEE THAT AGAIN!");
	out();
}

static void
cont(void)
{
	view();
	render();
	mapmus();
}

static void
ingam(void)
{
	if(qsp != ql+Lldsav4)
		greset();
	view();
}

static void
psych2(void)
{
	int n;

	n = (qtc - 1) * 16 + 6;
	if(nbrecv(csc, nil) > 0){
		n = 214;
		qp = qsp->e - 1;
		qtc = 0;
	}
	if(n > 214)
		n = 214;
	put(53, 101, n, 2, 0x37);
	put(53, 101, n-1, 1, 0x32);
	out();
}

static void
psych(void)
{
	ingam();
	mapmus();
	put(0, 0, Vw, Vhud, 0x7f);
	pic(48, 56, pict[Ppsyched]);
}

static void
indem(void)
{
	ginit(*demd++, -1, 0);
	if(demd >= epis)
		demd = dems;
	view();
}

static void
fixedw(char *s)
{
	char c;

	while(c = *s, c != 0)
		*s++ = c - '0' + 129;
}

static void
score(void)
{
	int x, y;
	char a[16], b[16];
	Score *s;

	mclear();
	stripe(10);
	pic(48, 0, pict[Pscores]);
	pic(32, 68, pict[Pname]);
	pic(160, 68, pict[Plvl]);
	pic(224, 68, pict[Phigh]);
	fnt = fnts;
	for(s=scs, y=76; s<scs+nelem(scs); s++, y+=16){
		txt(32, y, s->name, 0xf);
		sprint(a, "%d", s->lvl);
		fixedw(a);
		x = 176 - txtw(a);
		if(ver == WL6){
			sprint(b, "E%d/L", s->ep + 1);
			x += txt(x - 6, y, b, 0xf) - 6;
		}
		txt(x, y, a, 0xf);
		sprint(a, "%d", s->n);
		fixedw(a);
		txt(264 - txtw(a), y, a, 0xf);
	}
	if(qsp != ql+Lscore)
		mus(Mroster);
}
static void
sdscore(void)
{
	int y;
	char a[16];
	Score *s;

	mclear();
	pic(0, 0, pict[Pscores]);
	fnt = fnts+1;
	for(s=scs, y=76; s<scs+nelem(scs); s++, y+=16){
		txt(16, y, s->name, DIrhi);
		if(s->lvl == 21)
			pic(176, y-1, pict[Pspear]);
		else{
			sprint(a, "%d", s->lvl);
			txt(194 - txtw(a), y, a, 0xf);
		}
		sprint(a, "%d", s->n);
		txt(292 - txtw(a), y, a, 0xf);
	}
	if(qsp != ql+Lscore)
		mus(Maward);
}

static void
high(void)
{
	if(ver < SDM)
		score();
	else
		sdscore();
	inctl();
	grab(0);
}

static void
creds(void)
{
	pic(0, 0, pict[Pcreds]);
}

static void
title(void)
{
	pic(0, 0, pict[Ptitle1]);
	mus(Mintro);
}
static void
sdtitle(void)
{
	pic(0, 0, pict[Ptitle1]);
	pic(0, 80, pict[Ptitle2]);
	palpic(exts[Etitpal]);
	mus(Mtower);
}

static void
intro(void)
{
	fill(0x82);
	pic(216, 110, pict[Ppg13]);
}

static void
exit(void)
{
	wrconf();
	threadexitsall(nil);
}

static Sp
	loadq[] = {{30, fadeout}},
	introq[] = {{30, fadein}, {7*Tb, skip}, {30, fadeout}},
	titleq[] = {{30, fadein}, {15*Tb, skip}, {30, fadeout}},
	loopq[] = {{30, fadein}, {10*Tb, skip}, {30, fadeout}},
	scoreq[] = {{30, fadein}, {10*Tb, skip}, {30, fadeout}},
	pantsq[] = {{30, fadein}, {1, bwait}, {30, fadeout}},
	psychq[] = {{30, fadein}, {14, psych2}, {70, nbwait}, {30, fadeout}},
	gamq[] = {{30, fadein}, {1, game}, {41, fizz}, {1, gcont}, {30, fadeout}},
	gamsq[] = {{1, game}, {41, fizz}, {1, gcont}, {30, fadeout}},
	contq[] = {{30, fadein}, {1, gcont}, {30, fadeout}},
	msgq[] = {{1, bwait}, {1, gcont}, {30, fadeout}},
	gcontq[] = {{1, gcont}, {30, fadeout}},
	dieq[] = {{1, dieturn}, {144, fizz}, {100, nbwait}, {1, swait}},
	fdieq[] = {{0, ctl}, {1, blink}, {30, fadeout}, {1, mend2}, {30, fadein}},
	camq[] = {{100, nil}, {144, fizz}, {0, camtxt}, {300, nbwait}, {0, camtxt2}, {41, fizz}, {1, gcont}, {100, nil}, {0, stopmus}, {30, fadeout}},
	spq[] = {{1, swait}, {0, mapmus}, {1, gcont}, {30, fadeout}},
	interq[] = {{1, swait}, {30, fadein}},
	interiq[] = {{0, nil}, {1, interb}},
	intermq[] = {{0, nil}, {1, interm}, {1, interb}},
	interwq[] = {{1, interw}},
	intereq[] = {{0, nextmap}, {30, fadeout}},
	sinterq[] = {{1, swait}, {30, fadein}, {0, siscore}, {1, sinterw}, {0, nextmap}, {30, fadeout}},
	sdmq[] = {{1, bwait}, {30, fadeout}},
	colpq[] = {{300, fadeout}},
	colp2q[] = {{30, fadein}, {2*Tb, nil}, {0, colp}, {105, nil}, {0, colp}, {105, nil}, {0, colp}, {3*Tb, nil}},
	colp3q[] = {{5, fadeout}},
	wonq[] = {{1, swait}, {30, fadein}, {1, bwait}},
	won2q[] = {{0, nil}, {30, fadeout}},
	sdeq[] = {{30, fadein}, {1, bwait}, {30, fadeout}},
	presq[] = {{30, fadein}, {0, pres2}, {10*Tb, nbwait}, {0, pres3}, {10*Tb, nbwait}, {30, fadeout}},
	highq[] = {{30, fadein}, {1, bwait}, {30, fadeout}},
	escq[] = {{10, fadeout}},
	toctlq[] = {{10, fadein}},
	ctlq[] = {{0, blink}, {70, cwalk}, {0, blink}, {8, cwalk}},
	slq[] = {{1, slider}},
	curq[] = {{8, nil}, {0, cursfx}},
	togq[] = {{1, toggle}},
	promptq[] = {{0, iblink}, {Tb / 2, prompt}},
	diskq[] = {{1, disk}, {1, nil}},	/* buffer extra tics */
	mscoreq[] = {{10, fadein}, {1, bwait}, {10, fadeout}},
	quitq[] = {{0, pblink}, {10, ask}},
	ackq[] = {{1, bwait}},
	exitq[] = {{1, exit}};
static Seq ql[] = {
	[Lload] {nil, loadq, loadq+nelem(loadq), ql+Lintro, &fblk},
	[Lintro] {intro, introq, introq+nelem(introq), ql+Ltitle, &fblk},
	[Lftitle] {nil, escq, escq+nelem(escq), ql+Ltitle, &ftra},
	[Ltitle] {title, titleq, titleq+nelem(titleq), ql+Lcreds, &fblk},
	[Lcreds] {creds, loopq, loopq+nelem(loopq), ql+Lscore, &fblk},
	[Lscore] {score, loopq, loopq+nelem(loopq), ql+Ldemo, &fblk},
	[Lfpants] {nil, loadq, loadq+nelem(loadq), ql+Lpants, &fblk},
	[Lpants] {pants, pantsq, pantsq+nelem(pantsq), ql+Ltoctl, &fblk},
	[Ldemo] {indem, gamq, gamq+nelem(gamq), nil, &fblk},
	[Lpsych] {psych, psychq, psychq+nelem(psychq), ql+Lgame, &fblk},
	[Lgame] {view, gamq, gamq+nelem(gamq), nil, &fblk},
	[Lretry] {ingam, gamsq, gamsq+nelem(gamsq), nil},
	[Lmsg] {nil, msgq, msgq+nelem(msgq), nil, &fblk},
	[Lcont1] {nil, escq, escq+nelem(escq), ql+Lcont2, &ftra},
	[Lcont2] {cont, contq, contq+nelem(contq), nil, &fblk},
	[Lgcont] {cont, gcontq, gcontq+nelem(gcontq), nil, &fblk},
	[Lfdie] {nil, fdieq, fdieq+nelem(fdieq), ql+Ldie, &fblk},
	[Ldie] {die, dieq, dieq+nelem(dieq), nil},
	[Ldie2] {nil, loadq, loadq+nelem(loadq), ql+Lhigh, &fblk},
	[Lhigh] {high, highq, highq+nelem(highq), ql+Ltitle, &fblk},
	[Lcam] {nil, camq, camq+nelem(camq), nil, &fblk},
	[Lspear] {spshunt, spq, spq+nelem(spq), nil, &fblk},
	[Linter] {inter, interq, interq+nelem(interq), ql+Linteri, &fblk},
	[Linteri] {nil, interiq, interiq+nelem(interiq), nil},
	[Linterm] {nil, intermq, intermq+nelem(intermq), ql+Linteri},
	[Linterw] {nil, interwq, interwq+nelem(interwq), ql+Linterw},
	[Lintere] {nil, intereq, intereq+nelem(intereq), ql+Lpsych, &fblk},
	[Lsinter] {sinter, sinterq, sinterq+nelem(sinterq), ql+Lpsych, &fblk},
	[Lsdmend] {sdmend, sdmq, sdmq+nelem(sdmq), ql+Lhigh, &fblk},
	[Lcolp] {nil, colpq, colpq+nelem(colpq), ql+Lcolp2, &focl},
	[Lcolp2] {incolp, colp2q, colp2q+nelem(colp2q), ql+Lcolp3, &ficl},
	[Lcolp3] {nil, colp3q, colp3q+nelem(colp3q), ql+Lwon, &fecl},
	[Lwon] {won, wonq, wonq+nelem(wonq), ql+Lwon2, &fblk},
	[Lwon2] {nil, won2q, won2q+nelem(won2q), ql+Lhigh, &fblk},
	[Lsdepi] {sdepi, sdeq, sdeq+nelem(sdeq), ql+Lpres, &fblk},
	[Lpres] {pres1, presq, presq+nelem(presq), ql+Lroll1, &fblk},
	[Lroll1] {roll, sdeq, sdeq+nelem(sdeq), ql+Lroll2, &fblk},
	[Lroll2] {roll, sdeq, sdeq+nelem(sdeq), ql+Lroll3, &fblk},
	[Lroll3] {roll, sdeq, sdeq+nelem(sdeq), ql+Lroll4, &fblk},
	[Lroll4] {roll, sdeq, sdeq+nelem(sdeq), ql+Lroll5, &fblk},
	[Lroll5] {roll, sdeq, sdeq+nelem(sdeq), ql+Lroll6, &fblk},
	[Lroll6] {roll, sdeq, sdeq+nelem(sdeq), ql+Lroll7, &fblk},
	[Lroll7] {roll, sdeq, sdeq+nelem(sdeq), ql+Lhigh, &fblk},
	[Ldecay] {nil, loadq, loadq+nelem(loadq), ql+Ltoctl},
	[Ltoctl] {ctl, toctlq, toctlq+nelem(toctlq), ql+Lctl, &ftra},
	[Lftoctl] {nil, escq, escq+nelem(escq), ql+Lmtoctl, &fctl},
	[Lmtoctl] {ctl, toctlq, toctlq+nelem(toctlq), ql+Lctl, &fctl},
	[Lctl] {ctl, ctlq, ctlq+nelem(ctlq), ql+Lctl},
	[Lcur] {nil, curq, curq+nelem(curq), ql+Lctl},
	[Lftonew] {nil, escq, escq+nelem(escq), ql+Lmtonew, &fctl},
	[Lmtonew] {newgame, toctlq, toctlq+nelem(toctlq), ql+Lnctl, &fctl},
	[Lnewgame] {newgame, toctlq, toctlq+nelem(toctlq), ql+Lnctl, &fctl},
	[Lnctl] {newgame, ctlq, ctlq+nelem(ctlq), ql+Lnctl},
	[Ldenied] {denied, ackq, ackq+nelem(ackq), ql+Lnctl},
	[Ldifc1] {nil, escq, escq+nelem(escq), ql+Ldifc2, &fctl},
	[Ldifc2] {difc, toctlq, toctlq+nelem(toctlq), ql+Ldifc3, &fctl},
	[Ldifc3] {difc, ctlq, ctlq+nelem(ctlq), ql+Ldifc3},
	[Ldifc4] {nil, escq, escq+nelem(escq), ql+Ldifc5, &fctl},
	[Ldifc5] {setdifc, loadq, loadq+nelem(loadq), ql+Lpsych, &fblk},
	[Lfsnd] {nil, escq, escq+nelem(escq), ql+Lmsnd, &fctl},
	[Lmsnd] {snd, toctlq, toctlq+nelem(toctlq), ql+Lsctl, &fctl},
	[Lsctl] {snd, ctlq, ctlq+nelem(ctlq), ql+Lsctl},
	[Lsndtog] {nil, togq, togq+nelem(togq), ql+Lsctl},
	[Lfin] {nil, escq, escq+nelem(escq), ql+Lmin, &fctl},
	[Lmin] {in, toctlq, toctlq+nelem(toctlq), ql+Lictl, &fctl},
	[Lictl] {in, ctlq, ctlq+nelem(ctlq), ql+Lictl},
	[Lintog] {nil, togq, togq+nelem(togq), ql+Lictl},
	[Lfsav] {nil, escq, escq+nelem(escq), ql+Lmsav, &fctl},
	[Lmsav] {sav, toctlq, toctlq+nelem(toctlq), ql+Lsvctl, &fctl},
	[Lsvctl] {sav, ctlq, ctlq+nelem(ctlq), ql+Lsvctl},
	[Lsvname] {savname, promptq, promptq+nelem(promptq), ql+Lsvname},
	[Lwrsav] {nil, diskq, diskq+nelem(diskq), nil},
	[Lflod] {nil, escq, escq+nelem(escq), ql+Lmlod, &fctl},
	[Lmlod] {sav, toctlq, toctlq+nelem(toctlq), ql+Lldctl, &fctl},
	[Lldctl] {sav, ctlq, ctlq+nelem(ctlq), ql+Lldctl},
	[Lldsav] {nil, diskq, diskq+nelem(diskq), nil},
	[Lldsav2] {nil, escq, escq+nelem(escq), ql+Lldsav3, &fctl},
	[Lldsav3] {nil, loadq, loadq+nelem(loadq), ql+Lldsav4, &fblk},
	[Lldsav4] {psych, psychq, psychq+nelem(psychq), ql+Lgame, &fblk},
	[Lfsens] {nil, escq, escq+nelem(escq), ql+Lmsens, &fctl},
	[Lmsens] {sens, toctlq, toctlq+nelem(toctlq), ql+Lsectl, &fctl},
	[Lsectl] {sens, slq, slq+nelem(slq), ql+Lsectl},
	[Lfvw] {nil, escq, escq+nelem(escq), ql+Lmvw, &fctl},
	[Lmvw] {mvw, toctlq, toctlq+nelem(toctlq), ql+Lvwctl, &fctl},
	[Lvwctl] {mvw, slq, slq+nelem(slq), ql+Lvwctl},
	[Lfmscore] {nil, escq, escq+nelem(escq), ql+Lmscore, &fctl},
	[Lmscore] {score, mscoreq, mscoreq+nelem(mscoreq), ql+Lmtoctl, &fctl},
	[Lovrsav] {ovrsav, quitq, quitq+nelem(quitq), ql+Lovrsav},
	[Lend] {mend, quitq, quitq+nelem(quitq), ql+Lend},
	[Lcurgame] {curgame, quitq, quitq+nelem(quitq), ql+Lcurgame},
	[Lquit] {quit, quitq, quitq+nelem(quitq), ql+Lquit},
	[Lmexit] {nil, escq, escq+nelem(escq), ql+Lexit, &fctl},
	[Lexit] {nil, exitq, exitq+nelem(exitq)}
};

static void
initseqs(void)
{
	Item *i;

	mclear = wlmclear;
	stripe = wlstripe;
	quits = ends;
	if(ver == WL1){
		for(i=ml[LMnew].s+2; i<ml[LMnew].e; i+=2){
			i->c = DIeps;
			i->q = ql+Ldenied;
		}
	}
	if(ver >= SDM){
		mclear = sdmclear;
		stripe = sdstripe;
		ql[Ltitle].init = sdtitle;
		ql[Lscore].init = sdscore;
		ql[Lmscore].init = sdscore;
		ql[Lwon].f = &ficl;
		ql[Lwon2].q = ql+Lsdepi;
		fctl.c = (Col){0, 0, 0xce};
		mcol[DMbg] = 0x9d;
		quits += nelem(ends)/2;
	}
	mcol[DMoff] = mcol[DMbg] ^ 6;
	mcol[DMbrd] = mcol[DMbg] ^ 4;
	mcol[DMbrd2] = mcol[DMbg] ^ 14;
	ml[LMsnd].s[1].c = mcol[DMoff];
	ml[LMsnd].s[6].c = mcol[DMoff];
}

static void
conf(void)
{
	static int *vs[] = {&sfxon, &pcmon, &muson},
		is[] = {2, 0, 7, 5, 11, 10};
	int *ip, **vp;
	int n, m;

	muson = sfxon = pcmon = 1;
	grabon = 1;
	autorun = 0;
	msense = 5;
	vwsize = 15;
	rdconf();
	if(msense < 0)
		msense = 0;
	else if(msense > 9)
		msense = 9;
	if(vwsize < 4)
		vwsize = 4;
	else if(vwsize > 19)
		vwsize = 19;
	setvw();
	iin[0].a = grabon;
	iin[1].a = autorun;
	for(vp=vs, ip=is; vp<vs+nelem(vs); vp++){
		n = *ip++;
		m = *ip++;
		isnd[m].a = !(isnd[n].a = **vp);
		isnd[isnd[m].a ? n : m].q = ql+Lsndtog;
	}
}

static void
sqend(void)
{
	if(!gm.demo && !gm.record)
		return;
	qsp->q = gm.end == EDkey ? ql+Ltoctl : ql+Ltitle;
	if(demf != nil){
		free(demf);
		demf = nil;
		demd = dems;
		if(demexit)
			qsp->q = ql+Lexit;
	}
	gm.demo = gm.record = 0;
}
static void
edfizz(void)
{
	fizzop(-1, 1);
	put((Vw - vw.dx) / 2, (Vhud - vw.dy) / 2, vw.dx, vw.dy,
		qsp == ql+Lretry ? 4 : 0);
	out();
}
static void
edcam(void)
{
	gm.won++;
	out();
	fizzop(0x7f, 0);
	reset(ql+Lcam);
	qsp->q = ql+Lwon;
}
static void
eddie(void)
{
	u32int *p;

	p = pal;
	reset(ql+Ldie);
	pal = p;
	if(gm.lives >= 0)
		qsp->q = ql+Lretry;
	else
		qsp->q = ql+Ldie2;
}
static void
edup(void)
{
	gm.keys = 0;
	hudk();
	if(ver < SDM && gm.map % 10 == 9 || ver >= SDM
	&& (gm.map == 4 || gm.map == 9 || gm.map == 15 || gm.map >= 17)){
		gm.com = GMret;
		qsp->q = ql+Lsinter;
	}else if(gm.end == EDsetec){
		gm.com = GMsetec;
		qsp->q = ql+Linter;
	}else{
		gm.com = GMup;
		qsp->q = ql+Linter;
	}
}
static void
edspear(void)
{
	reset(ql+Lspear);
	qp->dt = pcmon ? 150 : 1;
	qp->f = pcmon ? nil : swait;
}
static void
edwon(void)
{
	stopmus();
	qsp->q = ql+Lwon;
	if(ver == SOD)
		reset(ql+Lcolp);
}
void
gend(void)
{
	kbon = 0;
	switch(gm.end){
	case EDfizz: edfizz(); break;
	case EDcam: edcam(); break;
	case EDcam2: scalspr(SPcam, vw.dx / 2, vw.dy + 1); sqend(); break;
	case EDdem: sqend(); break;
	case EDkey: qsp->q = ql+Ltoctl; sqend(); break;
	case EDdie: eddie(); break;
	case EDup: /* wet floor */
	case EDsetec: edup(); sqend(); break;
	case EDspear: edspear(); break;
	case EDwon: edwon(); sqend(); break;
	case EDmsg:;
	}
	gm.end = 0;
	pal = pals[Cfad];
	qtc = 0;
	step = qstep;
}

int
quickkey(Rune r)
{
	switch(r){
	case KF|1:
	case KF|2:
	case KF|3:
	case KF|4:
	case KF|5:
	case KF|6:
	case KF|7:
	case KF|8:
	case KF|9:
	default: return 0;
	case KF|10: reset(ql+Lquit); qesc = ql+Lgcont; break;
	}
	gm.end = EDmsg;
	grab(0);
	return 1;
}

void
qstep(void)
{
	Sp *p;

rep:
	p = qp;
	qtc += Δtc;
	if(p->f != nil)
		p->f();
	if(p != qp)
		return;
	if(qtc >= p->dt){
		if(++qp == qsp->e){
			reset(qsp->q);
			return;
		}
		qtc = 0;
	}
	if(p->dt == 0)
		goto rep;
}

void
init(char *f, int m, int d)
{
	srand(time(nil));
	conf();
	initseqs();
	inctl();
	demd = dems;
	reset(ql+Lload);
	if(m != -1){
		if(d > GDhard)
			d = GDhard;
		qsp->q = ql+Lpsych;
		ginit(nil, m, d);
		ingctl();
		return;
	}
	if(f != nil){
		demf = demof(f);
		demd = (uchar **)&demf;
		qsp->q = ql+Ldemo;
	}
	mus(ver<SDM ? Mintro : Mtower);
}