shithub: wl3d

ref: 35e90d35971631b97c76cb3a28ae222f8e6ba79b
dir: wl3d/map.c

View raw version
#include <u.h>
#include <libc.h>
#include "dat.h"
#include "fns.h"

Tile tiles[Mapa];
Obj *objs, *ofree, *oplr;
Door doors[Ndoor], *doore, pusher;
Static stcs[Nstc], *stce;
uchar plrarea[Narea], conarea[Narea*Narea];
int wspr[] = {SPknife, SPpistol, SPmg, SPgatling};

static int stctype[] = {
	Rnil, Rblock, Rblock, Rblock, Rnil, Rblock, Ralpo, Rblock, Rblock,
	Rnil, Rblock, Rblock, Rblock, Rblock, Rnil, Rnil, Rblock, Rblock,
	Rblock, Rnil, Rkey1, Rkey2, Rblock, Rnil, Rfood, Rstim, Rclip1,
	Rmg, Rchaingun, Rcross, Rchalice, Rbible, Rcrown, R1up, Rgibs, Rblock,
	Rblock, Rblock, Rgibs, Rblock, Rblock, Rnil, Rnil, Rnil, Rnil,
	Rblock, Rblock, Rnil, Rclip2, Rammobox, Rblock, Rspear, Rclip2
};
static Obj opool[Nobj+2];

static void
spawnstc(Tile *tl, int n)
{
	if(n >= nelem(stctype))
		sysfatal("invalid static object type %d", n);
	if(n > 48 && ver < SDM){
		fprint(2, "spawnstc: ignoring sod only static obj %d\n", n);
		return;
	}
	stce->f = 0;
	switch(stctype[n]){
	case Rnil:
	case Rclip2:
		break;
	case Rblock:
		tl->o = nil;
		tl->to = 1;
		break;
	case Rcross:
	case Rchalice:
	case Rbible:
	case Rcrown:
	case R1up:
		gm.ttot++;
		/* wet floor */
	default:
		stce->f = OFbonus;
		stce->item = stctype[n];
		break;
	}
	stce->tl = tl;
	n = stctype[n] == Rclip2 ? 28 : 2+n;
	stce->spr = sprs + n;
	if(stce->spr == nil)
		sysfatal("spawnstc: missing static sprite %d\n", n);
	if(++stce == stcs + nelem(stcs))
		sysfatal("static object overflow");
}

static void
rconair(int id)
{
	uchar *p, *a;

	a = conarea + id * Narea;
	p = plrarea;
	while(p < plrarea + nelem(plrarea)){
		if(*a && !*p){
			(*p)++;
			rconair(p-plrarea);
		}
		a++, p++;
	}
}
static void
conair(void)
{
	memset(plrarea, 0, sizeof plrarea);
	plrarea[oplr->areaid]++;
	rconair(oplr->areaid);
}

static void
dropening(Door *d)
{
	int δ, a1, a2, x, y;

	δ = d->dopen;
	if(δ == 0){
		a1 = d->tl[d->isvert ? +1 : -Mapdxy].p0 - MTfloor;
		a2 = d->tl[d->isvert ? -1 : +Mapdxy].p0 - MTfloor;
		conarea[a1*Narea + a2]++;
		conarea[a2*Narea + a1]++;
		conair();
		if(plrarea[a1]){
			x = ((d->tl-tiles) % Mapdxy << Dtlshift) + (1<<Dtlshift-1);
			y = ((d->tl-tiles) / Mapdxy << Dtlshift) + (1<<Dtlshift-1);
			sfxatt(Sopendoor, 1, x, y);
		}
	}
	δ += Δtc << 10;
	if(δ >= 0xffff){
		δ = 0xffff;
		d->tc = 0;
		d->φ = DRopen;
		d->tl->o = nil;
		d->tl->to = 0;
	}
	d->dopen = δ;
}

static void
drclosing(Door *d)
{
	int δ, a1, a2;

	if(d->tl->to != (d-doors | 0x80) || d->tl == oplr->tl){
		dropen(d);
		return;
	}
	δ = d->dopen - (Δtc << 10);
	if(δ <= 0){
		δ = 0;
		d->φ = DRshut;
		a1 = d->tl[d->isvert ? +1 : -Mapdxy].p0 - MTfloor;
		a2 = d->tl[d->isvert ? -1 : +Mapdxy].p0 - MTfloor;
		conarea[a1*Narea + a2]--;
		conarea[a2*Narea + a1]--;
		conair();
	}
	d->dopen = δ;
}

static void
drclose(Door *d)
{
	int tx, ty;
	Tile *tl;
	Obj *o1, *o2;

	tl = d->tl;
	if(tl->o != nil || tl->to != 0 || tl == oplr->tl)
		return;
	tx = (tl-tiles) % Mapdxy;
	ty = (tl-tiles) / Mapdxy;
	if(d->isvert){
		o1 = tl[-1].o;
		o2 = tl[+1].o;
		if(oplr->ty == ty
		&& (oplr->x + Dmin >> Dtlshift == tx
		|| oplr->x - Dmin >> Dtlshift == tx)
		|| o1 != nil && o1->x + Dmin >> Dtlshift == tx
		|| o2 != nil && o2->x - Dmin >> Dtlshift == tx)
			return;
	}else{
		o1 = tl[-Mapdxy].o;
		o2 = tl[+Mapdxy].o;
		if(oplr->tx == tx
		&& (oplr->y + Dmin >> Dtlshift == ty
		|| oplr->y - Dmin >> Dtlshift == ty)
		|| o1 != nil && o1->y + Dmin >> Dtlshift == ty
		|| o2 != nil && o2->y - Dmin >> Dtlshift == ty)
			return;
	}
	if(plrarea[tl->p0 - MTfloor]){
		tx = (tx << Dtlshift) + (1<<Dtlshift-1);
		ty = (ty << Dtlshift) + (1<<Dtlshift-1);
		sfxatt(Sclosedoor, 1, tx, ty);
	}
	d->φ = DRclosing;
	tl->to = d-doors | 0x80;
}

static void
drwait(Door *d)
{
	d->tc += Δtc;
	if(d->tc >= 300)
		drclose(d);
}

static void
udoors(void)
{
	Door *d;

	if(gm.won)
		return;
	for(d=doors; d<doore; d++)
		switch(d->φ){
		case DRopen: drwait(d); break;
		case DRopening: dropening(d); break;
		case DRclosing: drclosing(d); break;
		}
}

static void
spawndr(Tile *tl, int isvert, int lock)
{
	int n;
	Door *d;

	d = doore;
	n = d - doors;
	if(d >= doors + nelem(doors))
		sysfatal("spawndr: door overflow");
	d->tl = tl;
	d->isvert = isvert;
	d->lock = lock;
	d->φ = DRshut;
	d->dopen = 0;
	tl->o = nil;
	tl->to = n | 0x80;
	tl->tl = n | 0x80;
	if(isvert){
		tl->p0 = tl[-1].p0;
		tl[-Mapdxy].tl |= 0x40;
		tl[+Mapdxy].tl |= 0x40;
	}else{
		tl->p0 = tl[-Mapdxy].p0;
		tl[-1].tl |= 0x40;
		tl[+1].tl |= 0x40;
	}
	doore++;
}

static void
upush(void)
{
	int n;
	Tile *tl;

	if(pusher.φ == 0)
		return;
	n = pusher.φ >> 7;
	pusher.φ += Δtc;
	pusher.dopen = pusher.φ >> 1 & 63;
	if(pusher.φ >> 7 == n)
		return;
	tl = pusher.tl;
	n = tl->tl & 63;
	tl->tl = 0;
	tl->o = nil;
	tl->to = 0;
	tl->p0 = oplr->areaid + MTfloor;
	if(pusher.φ > 256){
		pusher.φ = 0;
		return;
	}
	switch(pusher.isvert){
	case θN: pusher.tl -= Mapdxy; tl -= Mapdxy * 2; break;
	case θE: pusher.tl++; tl += 2; break;
	case θS: pusher.tl += Mapdxy; tl += Mapdxy * 2; break;
	case θW: pusher.tl--; tl -= 2; break;
	}
	if(tl->o != nil || tl->to != 0){
		pusher.φ = 0;
		return;
	}
	tl->to = n;
	tl->tl = n;
	pusher.tl->tl = n | 0xc0;
}

static void
oswap(Obj *o, Obj *r, int z)
{
	o->p->n = o->n;
	o->n->p = o->p;
	if(z)
		memset(o, 0, sizeof *o);
	o->n = r;
	o->p = r->p;
	r->p->n = o;
	r->p = o;
}

static void
odel(Obj *o)
{
	if(o == oplr)
		sysfatal("odel: player deletion");
	oswap(o, ofree, 1);
}

static void
oinit(void)
{
	Obj *o, *p;

	memset(opool, 0, sizeof opool);
	objs = opool;
	ofree = opool+1;
	oplr = opool+2;
	objs->n = objs->p = oplr;
	oplr->n = oplr->p = objs;
	p = ofree;
	o = oplr + 1;
	while(o < opool + nelem(opool)){
		o->p = p;
		p->n = o;
		p = o;
		o++;
	}
	ofree->p = p;
	p->n = ofree;
}

static void
up(Obj *o, Obj **n)
{
	if(o->s->up != nil){
		o->s->up(o);
		*n = o->n;
		if(o->s == nil){
			odel(o);
			return;
		}
	}
	if(o->f & OFnevermark || o->f & OFnomark && o->tl->o != nil)
		return;
	o->tl->o = o;
	o->tl->to = 0;
}

static void
uobj(Obj *o, Obj **n)
{
	if(!o->on && !plrarea[o->areaid])
		return;
	if((o->f & (OFnomark | OFnevermark)) == 0)
		o->tl->o = nil;
	if(o->tc == 0){
		up(o, n);
		return;
	}
	o->tc -= Δtc;
	while(o->tc <= 0){
		if(o->s->act != nil){
			o->s->act(o);
			if(o->s == nil){
				*n = o->n;
				odel(o);
				return;
			}
		}
		o->s = o->s->n;
		*n = o->n;
		if(o->s == nil){
			odel(o);
			return;
		}
		if(o->s->dt == 0){
			o->tc = 0;
			break;
		}
		o->tc += o->s->dt;
	}
	up(o, n);
}

static u16int
unmark(Tile *tl)
{
	u16int n;

	tl->tl = 0;
	n = tl->p0;
	if(tl - 1 < tiles || tl - Mapdxy < tiles
	|| tl + 1 > tiles+nelem(tiles) || tl + Mapdxy > tiles+nelem(tiles))
		sysfatal("unmark: tile out of range");
	if(tl[1].p0 >= MTfloor)
		n = tl[1].p0;
	if(tl[-Mapdxy].p0 >= MTfloor)
		n = tl[-Mapdxy].p0;
	if(tl[Mapdxy].p0 >= MTfloor)
		n = tl[Mapdxy].p0;
	if(tl[-1].p0 >= MTfloor)
		n = tl[-1].p0;
	return n;
}

static void
spawnplr(Tile *tl, int dir)
{
	oplr->s = stt+GSplr;
	oplr->type = Oplr;
	oplr->on++;
	oplr->tl = tl;
	oplr->tx = (tl-tiles) % Mapdxy;
	oplr->ty = (tl-tiles) / Mapdxy;
	osetglobal(oplr);
	oplr->areaid = tl->p0 - MTfloor;
	oplr->θ = (450 - dir * 90) % 360;
	oplr->f |= OFnevermark;
	plrarea[oplr->areaid]++;
}

static void
spawnghost(Tile *tl, State *s)
{
	Obj *o;

	if(ver >= SDM)
		return;
	o = ospawn(tl, s);
	o->type = Oghost;
	o->v = 1500;
	o->θ = θE;
	o->f |= OFambush;
	gm.ktot++;
}

static void
spawnboss(Tile *tl, int type)
{
	int hp, θ;
	State *s;
	Obj *o;

	/* bug: pcmon checks cut demo playback short before cam can be called
	 * if they were recorded with pcmon=0 */
	θ = θnil;
	s = nil;
	hp = 0;
	switch(type){
	wlonly:
		if(ver >= SDM){
			fprint(2, "spawnboss: non-wl6 obj type %d\n", type);
			return;
		}
		break;
	sdonly:
		if(ver < SDM){
			fprint(2, "spawnboss: non-sod obj type %d\n", type);
			return;
		}
		break;
	case Ohans:
		s = stt+GShans;
		hp = gm.difc<GDhard ? 850 + gm.difc * 100 : 1200;
		θ = θS;
		goto wlonly;
	case Oschb:
		s = stt+GSschb;
		hp = gm.difc<GDeasy ? 850 : gm.difc<GDmed ? 950
			: gm.difc<GDhard ? 1550 : 2400;
		θ = θS;
		goto wlonly;
	case Ogretel:
		s = stt+GSgretel;
		hp = gm.difc<GDhard ? 850 + gm.difc * 100 : 1200;
		θ = θN;
		goto wlonly;
	case Ootto:
		s = stt+GSotto;
		hp = gm.difc<GDhard ? 850 + gm.difc * 100 : 1200;
		θ = θN;
		goto wlonly;
	case Ofett:
		s = stt+GSfett;
		hp = gm.difc<GDhard ? 850 + gm.difc * 100 : 1200;
		θ = θS;
		goto wlonly;
	case Ofake:
		s = stt+GSfake;
		hp = 200 + 100 * gm.difc;
		θ = θN;
		goto wlonly;
	case Omech:
		s = stt+GSmech;
		hp = gm.difc<GDeasy ? 800 : gm.difc<GDmed ? 950
			: gm.difc<GDhard ? 1050 : 1200;
		θ = θS;
		goto wlonly;
	case Otrans:
		s = stt+GStrans;
		hp = gm.difc<GDhard ? 850 + gm.difc * 100 : 1200;
		goto sdonly;
	case Owilh:
		s = stt+GSwilh;
		hp = gm.difc<GDhard ? 950 + gm.difc * 100 : 1300;
		goto sdonly;
	case Ouber:
		s = stt+GSuber;
		hp = gm.difc<GDhard ? 1050 + gm.difc * 100 : 1400;
		goto sdonly;
	case Oknight:
		s = stt+GSknight;
		hp = gm.difc<GDhard ? 1250 + 100 * gm.difc : 1600;
		goto sdonly;
	case Ospectre:
		s = stt+GSspectrewait1;
		hp = gm.difc<GDhard ? 5 * (1 + gm.difc) : 25;
		goto sdonly;
	case Oangel:
		s = stt+GSangel;
		hp = gm.difc<GDhard ? 1450 + 100 * gm.difc : 2000;
		goto sdonly;
	}
	o = ospawn(tl, s);
	o->type = type;
	o->hp = hp;
	o->θ = θ;
	o->f |= OFshootable | OFambush;
	gm.ktot++;
}

static void
spawndeadgd(Tile *tl)
{
	Obj *o;

	o = ospawn(tl, stt+GSgddie4);
	o->type = Oinert;
}

static void
spawnguy(Tile *tl, int type, int dir, int patrol)
{
	int hp;
	Obj *o;
	State *s;

	s = nil;
	hp = 0;
	switch(type){
	case Ogd:
		s = stt + (patrol ? GSgdwalk1 : GSgd);
		hp = 25;
		break;
	case Oofc:
		s = stt + (patrol ? GSofcwalk1 : GSofc);
		hp = 50;
		break;
	case Omut:
		s = stt + (patrol ? GSmutwalk1 : GSmut);
		hp = gm.difc > GDmed ? 65 : gm.difc > GDbaby ? 55 : 45;
		break;
	case Oss:
		s = stt + (patrol ? GSsswalk1 : GSss);
		hp = 100;
		break;
	case Odog:
		/* bug: unhandled case causing object pool corruption */
		if(!patrol)
			sysfatal("spawnguy: unhandled spawn type");
		s = stt + GSdogwalk1;
		hp = 1;
		break;
	}
	o = ospawn(tl, s);
	if(patrol){
		tl->o = nil;
		tl->to = 0;
		switch(dir){
		case 0: tl++; o->tx++; break;
		case 1: tl -= Mapdxy; o->ty--; break;
		case 2: tl--; o->tx--; break;
		case 3: tl += Mapdxy; o->ty++; break;
		}
		tl->to = 0;
		tl->o = o;
		o->tl = tl;
	}
	o->type = type;
	o->f |= OFshootable;
	o->hp = hp;
	o->v = type == Odog ? 1500 : 512;
	o->θ = dir * 90;

	gm.ktot++;
	if(patrol){
		o->Δr = Dtlglobal;
		o->on++;
	}else if(tl->p0 == MTambush){
		tl->p0 = unmark(tl);
		o->f |= OFambush;
		o->areaid = tl->p0 - MTfloor;
	}
}

static void
spawn(Tile *tl)
{
	int n, difc;

	n = tl->p1;
	difc = GDbaby;
	switch(n){
	case 19: case 20: case 21: case 22:
		spawnplr(tl, n-19);
		break;
	case 23: case 24: case 25: case 26: case 27: case 28: case 29: case 30:
	case 31: case 32: case 33: case 34: case 35: case 36: case 37: case 38:
	case 39: case 40: case 41: case 42: case 43: case 44: case 45: case 46:
	case 47: case 48: case 49: case 50: case 51: case 52: case 53: case 54:
	case 55: case 56: case 57: case 58: case 59: case 60: case 61: case 62:
	case 63: case 64: case 65: case 66: case 67: case 68: case 69: case 70:
	case 71: case 72: case 73: case 74:
		spawnstc(tl, n-23);
		break;
	case 98:
		gm.stot++;
		break;
	case 180: case 181: case 182: case 183: difc++;	n-=36; /* wet floor */
	case 144: case 145: case 146: case 147: difc+=2; n-=36; /* wet floor */
	case 108: case 109: case 110: case 111:
		if(difc <= gm.difc)
			spawnguy(tl, Ogd, n-108, 0);
		break;
	case 184: case 185: case 186: case 187: difc++;	n-=36; /* wet floor */
	case 148: case 149: case 150: case 151: difc+=2; n-=36; /* wet floor */
	case 112: case 113: case 114: case 115:
		if(difc <= gm.difc)
			spawnguy(tl, Ogd, n-112, 1);
		break;
	case 188: case 189: case 190: case 191: difc++;	n-=36; /* wet floor */
	case 152: case 153: case 154: case 155: difc+=2; n-=36; /* wet floor */
	case 116: case 117: case 118: case 119:
		if(difc <= gm.difc)
			spawnguy(tl, Oofc, n-116, 0);
		break;
	case 192: case 193: case 194: case 195: difc++;	n-=36; /* wet floor */
	case 156: case 157: case 158: case 159: difc+=2; n-=36; /* wet floor */
	case 120: case 121: case 122: case 123:
		if(difc <= gm.difc)
			spawnguy(tl, Oofc, n-120, 1);
		break;
	case 198: case 199: case 200: case 201: difc++;	n-=36; /* wet floor */
	case 162: case 163: case 164: case 165: difc+=2; n-=36; /* wet floor */
	case 126: case 127: case 128: case 129:
		if(difc <= gm.difc)
			spawnguy(tl, Oss, n-126, 0);
		break;
	case 202: case 203: case 204: case 205: difc++;	n-=36; /* wet floor */
	case 166: case 167: case 168: case 169: difc+=2; n-=36; /* wet floor */
	case 130: case 131: case 132: case 133:
		if(difc <= gm.difc)
			spawnguy(tl, Oss, n-130, 1);
		break;
	case 206: case 207: case 208: case 209: difc++;	n-=36; /* wet floor */
	case 170: case 171: case 172: case 173: difc+=2; n-=36; /* wet floor */
	case 134: case 135: case 136: case 137:
		if(difc <= gm.difc)
			spawnguy(tl, Odog, n-134, 0);
		break;
	case 210: case 211: case 212: case 213: difc++;	n-=36; /* wet floor */
	case 174: case 175: case 176: case 177: difc+=2; n-=36; /* wet floor */
	case 138: case 139: case 140: case 141:
		if(difc <= gm.difc)
			spawnguy(tl, Odog, n-138, 1);
		break;
	case 252: case 253: case 254: case 255: difc++;	n-=18; /* wet floor */
	case 234: case 235: case 236: case 237: difc+=2; n-=18; /* wet floor */
	case 216: case 217: case 218: case 219:
		if(difc <= gm.difc)
			spawnguy(tl, Omut, n-216, 0);
		break;
	case 256: case 257: case 258: case 259: difc++;	n-=18; /* wet floor */
	case 238: case 239: case 240: case 241: difc+=2; n-=18; /* wet floor */
	case 220: case 221: case 222: case 223:
		if(difc <= gm.difc)
			spawnguy(tl, Omut, n-220, 1);
		break;
	case 224: case 225: case 226: case 227:
		spawnghost(tl, stt+GSgh1chase1+n-224);
		break;
	case 106: spawnboss(tl, Ospectre); break;
	case 107: spawnboss(tl, Oangel); break;
	case 124: spawndeadgd(tl); break;
	case 125: spawnboss(tl, Otrans); break;
	case 142: spawnboss(tl, Ouber); break;
	case 143: spawnboss(tl, Owilh); break;
	case 160: spawnboss(tl, Ofake); break;
	case 161: spawnboss(tl, Oknight); break;
	case 178: spawnboss(tl, Omech); break;
	case 179: spawnboss(tl, Ofett); break;
	case 196: spawnboss(tl, Oschb); break;
	case 197: spawnboss(tl, Ogretel); break;
	case 214: spawnboss(tl, Ohans); break;
	case 215: spawnboss(tl, Ootto); break;
	}
}

void
drop(Tile *tl, int n)
{
	int sn, *sti;
	Static *s;

	for(sti=stctype; sti<stctype+nelem(stctype); sti++)
		if(*sti == n)
			break;
	if(sti >= stctype + nelem(stctype))
		sysfatal("drop: unknown item type");
	for(s=stcs; s<stcs+nelem(stcs); s++)
		if(s->tl == nil){
			if(s == stce)
				stce++;
			break;
		}
	if(s >= stcs + nelem(stcs))
		return;
	s->tl = tl;
	sn = n == Rclip2 ? 28 : 2+(sti-stctype);
	s->spr = sprs + sn;
	if(s->spr == nil)
		sysfatal("drop: missing static sprite %d\n", sn);
	s->f = OFbonus;
	s->item = n;
}

void
dropen(Door *d)
{
	if(d->φ == DRopen)
		d->tc = 0;
	else
		d->φ = DRopening;
}

void
druse(Door *d)
{
	if(d->lock > DRunlk && d->lock < DRup && ~gm.keys & 1<<d->lock-DRlock1){
		sfx(Snoway);
		return;
	}
	switch(d->φ){
	case DRshut: case DRclosing: dropen(d); break;
	case DRopen: case DRopening: drclose(d); break;
	}
}

void
osetglobal(Obj *o)
{
	if(o->tx > Mapdxy || o->ty > Mapdxy)
		sysfatal("object %d,%d out of bounds", o->tx, o->ty);
	o->x = (o->tx << Dtlshift) + Dtlglobal / 2;
	o->y = (o->ty << Dtlshift) + Dtlglobal / 2;
}

void
ostate(Obj *o, State *s)
{
	o->s = s;
	o->tc = s->dt;
}

Obj *
onew(void)
{
	Obj *o;

	if(ofree->p == ofree)
		sysfatal("onew: object list overflow");
	o = ofree->p;
	oswap(o, objs, 0);
	return o;
}

Obj *
ospawn(Tile *tl, State *s)
{
	Obj *o;

	o = onew();
	tl->o = o;
	tl->to = 0;
	o->tl = tl;
	o->s = s;
	o->tx = (tl-tiles) % Mapdxy;
	o->ty = (tl-tiles) / Mapdxy;
	osetglobal(o);
	o->areaid = tl->p0 - MTfloor;
	if(s->dt != 0){
		o->tc = rnd() % s->dt;
		/* bug: if .tc is 0, uobj won't update its state on its own,
		 * and moving objects randomly won't change their sprite */
		if(!gm.record && !gm.demo && o->tc == 0)
			o->tc = 1;
	}else
		o->tc = 0;
	return o;
}

void
uworld(void)
{
	Obj *o, *n;

	udoors();
	upush();
	for(o=oplr; o!=objs; o=n){
		n = o->n;
		uobj(o, &n);
	}
}

void
mapmus(void)
{
	static char wlmus[] = {
		3, 11, 9, 12, 3, 11, 9, 12, 2, 0,
		8, 18, 17, 4, 8, 18, 4, 17, 2, 1,
		6, 20, 22, 21, 6, 20, 22, 21, 19, 26,
		3, 11, 9, 12, 3, 11, 9, 12, 2, 0,
		8, 18, 17, 4, 8, 18, 4, 17, 2, 1,
		6, 20, 22, 21, 6, 20, 22, 21, 19, 15
	}, sdmus[] = {
		4, 0, 2, 22, 15, 1, 5, 9, 10, 15,
		8, 3, 12, 11, 13, 15, 21, 15, 18, 0, 17
	};

	mus(ver < SDM ? wlmus[gm.map] : sdmus[gm.map]);
}

void
wrmap(void)
{
	Tile *tl;
	Obj *o;
	Static *s;
	Door *d;

	for(tl=tiles; tl<tiles+nelem(tiles); tl++)
		pack("WWBB", tl->p0, tl->p1, tl->tl, tl->to);
	pack("nn", conarea, sizeof conarea, plrarea, sizeof plrarea);
	disking();
	for(o=oplr; o!=objs; o=o->n)
		pack("wwwwbdddwwbwwdwwdww", o->on, o->tc, o->type, (int)(o->s - stt),
			o->f, o->Δr, o->x, o->y, o->tx, o->ty, o->areaid,
			o->vwx, o->vwdy, o->vwdx, o->θ, o->hp, o->v, o->atkdt,
			o->sdt);
	disking();
	pack("ww", 0xffff, (int)(stce - stcs));
	for(s=stcs; s<stce; s++)
		pack("wwbb", s->tl != nil ? (int)(s->tl - tiles) : 0xffff,
			(int)(s->spr - sprs), s->f, s->item);
	pack("w", (int)(doore - doors));
	for(d=doors; d<doore; d++)
		pack("wbbwwW", (int)(d->tl - tiles), d->isvert, d->lock, d->φ,d->tc,
			d->dopen);
	pack("wwwW", pusher.φ, (int)(pusher.tl - tiles), pusher.isvert, pusher.dopen);
}

static void
sttdtinit(void)
{
	/* bug: die state durations are set on spawn and persist regardless of
	 * changes in sound settings, until map load; sod: durations persist
	 * across maps */
	if(pcmon){
		stt[GSschbdie2].dt = 140;
		stt[GSottodie2].dt = 140;
		stt[GSfettdie2].dt = 140;
		/* bug?: set for Ofake as well */
		stt[GShitlerdie2].dt = 140;
		stt[GStransdie2].dt = 105;
		stt[GSuberdie2].dt = 70;
		stt[GSknightdie2].dt = 105;
		stt[GSangeldie2].dt = 105;
	}else{
		stt[GSschbdie2].dt = 5;
		stt[GSottodie2].dt = 5;
		stt[GSfettdie2].dt = 5;
		stt[GShitlerdie2].dt = 5;
		stt[GStransdie2].dt = 1;
		stt[GSuberdie2].dt = 1;
		stt[GSknightdie2].dt = 10;
		stt[GSangeldie2].dt = 1;
	}
}

static void
nukemap(void)
{
	memset(tiles, 0, sizeof tiles);
	memset(plrarea, 0, sizeof plrarea);
	memset(conarea, 0, sizeof conarea);
	memset(doors, 0, sizeof doors);
	memset(stcs, 0, sizeof stcs);
	doore = doors;
	stce = stcs;
	oinit();
	sttdtinit();
}

void
ldmap(void)
{
	int n, m;
	Tile *tl;
	Obj *o;
	Static *s;
	Door *d;

	nukemap();
	for(tl=tiles; tl<tiles+nelem(tiles); tl++)
		unpack("WWBB", &tl->p0, &tl->p1, &tl->tl, &tl->to);
	unpack("nn", conarea, sizeof conarea, plrarea, sizeof plrarea);
	disking();
	for(o=nil;;){
		unpack("w", &n);
		if(n == 0xffff)
			break;
		o = o == nil ? oplr : onew();
		o->on = n;
		unpack("swwbSddwwbwwdswdsw", &o->tc, &o->type, &n, &o->f,
			&o->Δr, &o->x, &o->y, &o->tx, &o->ty, &o->areaid,
			&o->vwx, &o->vwdy, &o->vwdx, &o->θ, &o->hp, &o->v,
			&o->atkdt, &o->sdt);
		o->s = stt + n;
		o->tl = tiles + o->ty * Mapdxy + o->tx;
		if(o != oplr && ((o->f & OFnevermark) == 0
		|| (o->f & OFnomark) == 0 || o->tl->o == nil)){
			o->tl->o = o;
			o->tl->to = 0;
		}
	}
	disking();
	unpack("w", &n);
	stce = stcs + n;
	if(stce > stcs + nelem(stcs))
		sysfatal("ldmap: static object overflow");
	for(s=stcs; s<stce; s++){
		unpack("wwbb", &n, &m, &s->f, &s->item);
		s->tl = n == 0xffff ? nil : tiles + n;
		s->spr = sprs + m;
	}
	unpack("w", &n);
	doore = doors + n;
	if(doore > doors + nelem(doors))
		sysfatal("ldmap: door overflow");
	for(d=doors; d<doore; d++){
		unpack("wbbwwW", &n, &d->isvert, &d->lock, &d->φ, &d->tc,
			&d->dopen);
		d->tl = tiles + n;
	}
	unpack("wwwW", &pusher.φ, &n, &pusher.isvert, &pusher.dopen);
	pusher.tl = tiles + n;
}

void
initmap(void)
{
	u16int *p0, *p1, *s;
	Tile *tl;

	nukemap();
	p0 = s = readmap(gm.map);
	p1 = p0 + Mapa;
	for(tl=tiles; tl<tiles+nelem(tiles); tl++){
		tl->p0 = *p0++;
		tl->p1 = *p1++;
		if(tl->p0 < MTfloor){
			tl->tl = tl->p0;
			tl->to = tl->p0;
		}
	}
	free(s);
	for(tl=tiles; tl<tiles+nelem(tiles); tl++)
		if(tl->p0 > 89 && tl->p0 < 102)
			spawndr(tl, ~tl->p0 & 1, (tl->p0 - 90) / 2);
	for(tl=tiles; tl<tiles+nelem(tiles); tl++)
		spawn(tl);
	for(tl=tiles; tl<tiles+nelem(tiles); tl++)
		if(tl->p0 == MTambush){
			if(tl->to == MTambush)
				tl->to = 0;
			tl->p0 = unmark(tl);
		}
}

void
sodmap(void)
{
	int *w;
	State *s;

	stctype[15] = Rblock;
	stctype[40] = Rnil;
	stctype[44] = Rblock;
	stctype[48] = Rblock;
	for(s=stt+GSgd; s<stt+GShans; s++)
		s->spr += 4;
	for(s=stt+GSmissile; s<stt+GSrocket; s++)
		s->spr -= SPmissile1 - SPofcfire3 - 1 - 4;
	for(w=wspr; w<wspr+nelem(wspr); w++)
		*w += SPangeldead - SPbjjump4;
}