shithub: sce

Download patch

ref: 949f77d0b45957f6c0ee4160ef1236d0d62369d7
parent: d0e226919d3d681e36cdd2476324dc0c973774f6
author: qwx <qwx@sciops.net>
date: Sun Jul 4 17:07:51 EDT 2021

drw/sim: add immutable neutral objects, minerals

- fs: minerals have 4 states, based on how much of the resource remains;
Obj .pics[] state is overloaded for this;
a flag was added for spr records to differentiate between these states;
not the cleanest way, but...
- fs: remove PFtile, never used for spr records
- remove Mobj .f, for now obj flags are never modified
- db: put mapobj coordinates first before additional fields
- sim: teams[] tracks 8 teams + neutral, later possibly others;
that way neutrals are treated same as any other object
- drw: special case for resource objects
- add some additional sanity checks

--- a/com.c
+++ b/com.c
@@ -66,7 +66,7 @@
 	Team *t;
 
 	n = r->idx >> Teamshift & Nteam - 1;
-	if(n > nteam){
+	if(n < 0 || n > nteam){
 		werrstr("invalid team number %d", n);
 		return nil;
 	}
@@ -97,19 +97,21 @@
 	&reqm.idx, &reqm.uuid, &reqm.x, &reqm.y,
 	&click.x, &click.y,
 	&reqt.idx, &reqt.uuid, &reqt.x, &reqt.y)) < 0)
-		goto error;
+		return -1;
 	if((mo = getmobj(&reqm)) == nil)
-		goto error;
+		return -1;
+	if((mo->o->f & Fimmutable) || mo->o->speed == 0.0){
+		werrstr("reqmovenear: object %s can't move", mo->o->name);
+		return -1;
+	}
 	if((tgt = getmobj(&reqt)) == nil)
-		goto error;
+		return -1;
 	if(click.x >= nodemapwidth || click.y >= nodemapheight){
-		werrstr("reqmove: invalid location %d,%d", click.x, click.y);
+		werrstr("reqmovenear: invalid location %d,%d", click.x, click.y);
 		return -1;
 	}
 	moveone(click, mo, tgt);
 	return n;
-error:
-	return -1;
 }
 
 static int
@@ -122,9 +124,13 @@
 	if((n = unpack(p, e, "dldd dd",
 	&reqm.idx, &reqm.uuid, &reqm.x, &reqm.y,
 	&tgt.x, &tgt.y)) < 0)
-		goto error;
+		return -1;
 	if((mo = getmobj(&reqm)) == nil)
-		goto error;
+		return -1;
+	if((mo->o->f & Fimmutable) || mo->o->speed == 0.0){
+		werrstr("reqmove: object %s can't move", mo->o->name);
+		return -1;
+	}
 	if(tgt.x >= nodemapwidth || tgt.y >= nodemapheight){
 		werrstr("reqmove: invalid target %d,%d", tgt.x, tgt.y);
 		return -1;
@@ -131,8 +137,6 @@
 	}
 	moveone(tgt, mo, nil);
 	return n;
-error:
-	return -1;
 }
 
 static int
--- a/dat.h
+++ b/dat.h
@@ -5,6 +5,8 @@
 typedef struct Pics Pics;
 typedef struct Obj Obj;
 typedef struct Path Path;
+typedef struct Munit Munit;
+typedef struct Mresource Mresource;
 typedef struct Mobj Mobj;
 typedef struct Mobjl Mobjl;
 typedef struct Tile Tile;
@@ -19,7 +21,8 @@
 	Nselect = 12,
 	Nrot = 32,
 	/* oh boy */
-	Nteambits = 3,
+	Nplayteam = 8,		/* non-neutral player teams */
+	Nteambits = 4,
 	Nteam = 1 << Nteambits,
 	Teamshift = 32 - Nteambits,
 	Teamidxmask = ~(Nteam - 1 << Teamshift),
@@ -63,21 +66,6 @@
 extern Node *nodemap;
 extern int nodemapwidth, nodemapheight;
 
-struct Attack{
-	char *name;
-	int dmg;
-	int range;
-	int cool;
-};
-
-enum{
-	PFtile = 1<<0,
-	PFidle = 1<<1,
-	PFmove = 1<<2,
-	PFglow = 1<<13,
-	PFalpha = 1<<14,
-	PFshadow = 1<<15,
-};
 struct Pic{
 	u32int *p;
 	int w;
@@ -93,11 +81,20 @@
 	int iscopy;
 };
 
+struct Attack{
+	char *name;
+	int dmg;
+	int range;
+	int cool;
+};
+
 enum{
 	Fbio = 1<<0,
 	Fmech = 1<<1,
 	Fair = 1<<2,
 	Fbuild = 1<<3,
+	Fresource = 1<<14,
+	Fimmutable = 1<<15,
 };
 enum{
 	PTbase,
@@ -105,10 +102,37 @@
 	PTglow,
 	PTend,
 
-	OSidle = 0,
-	OSmove,
-	OSend,
+	OState0 = 0,
+	OState1,
+	OState2,
+	OState3,
+ 	OSend,
+
+	/* unit */
+	OSidle = OState0,
+	OSmove = OState1,
+
+	/* resource */
+	OSrich = OState0,
+	OSmed = OState1,
+	OSlow = OState2,
+	OSpoor = OState3,
 };
+enum{
+	PFidle = OSidle,
+	PFmove = OSmove,
+	PFrich = OSrich,
+	PFmed = OSmed,
+	PFlow = OSlow,
+	PFpoor = OSpoor,
+	PFstatemask = (1 << 5) - 1,
+
+	PFimmutable = 1<<12,
+	PFglow = 1<<13,
+	PFalpha = 1<<14,
+	PFshadow = 1<<15,
+	PFtile = 1<<16,
+};
 struct Obj{
 	char *name;
 	Pics pics[OSend][PTend];
@@ -138,17 +162,15 @@
 	Point *pathp;
 	Point *pathe;
 };
-struct Mobj{
-	Obj *o;
-	int idx;
-	long uuid;
+struct Mresource{
+	int amount;
+};
+struct Munit{
 	int state;
+	int team;
+	int hp;
+	int xp;
 	int freezefrm;
-	Point;
-	int px;
-	int py;
-	int subpx;
-	int subpy;
 	double θ;
 	double Δθ;
 	int Δθs;
@@ -158,10 +180,18 @@
 	double speed;
 	Mobjl *movingp;
 	Mobjl *mobjl;
-	int f;
-	int team;
-	int hp;
-	int xp;
+};
+struct Mobj{
+	Obj *o;
+	int idx;
+	long uuid;
+	Point;
+	int px;
+	int py;
+	int subpx;
+	int subpy;
+	Munit;
+	Mresource;
 };
 struct Mobjl{
 	Mobj *mo;
--- a/drw.c
+++ b/drw.c
@@ -75,7 +75,7 @@
 	Mobj *mo, *it;
 
 	it = selected[0];
-	if(!ptinrect(p, selr) || it == nil)
+	if(it == nil || it->o->f & Fimmutable || !ptinrect(p, selr))
 		return;
 	vp = divpt(subpt(p, selr.min), scale);
 	i = fbvis[vp.y * fbw + vp.x];
@@ -107,7 +107,10 @@
 	mo = selected[0];
 	if(mo == nil)
 		return;
-	snprint(s, sizeof s, "%s %d/%d", mo->o->name, mo->hp, mo->o->hp);
+	if(mo->o->f & Fresource)
+		snprint(s, sizeof s, "%s %d", mo->o->name, mo->amount);
+	else
+		snprint(s, sizeof s, "%s %d/%d", mo->o->name, mo->hp, mo->o->hp);
 	string(screen, p0, display->white, ZP, font, s);
 }
 
@@ -300,6 +303,16 @@
 	}
 }
 
+static int
+stateframe(Mobj *mo)
+{
+	/* FIXME: will be replaced */
+	if(mo->o->f & Fresource){
+		return OSrich;
+	}
+	return mo->state;
+}
+
 static Pic *
 frm(Mobj *mo, int type)
 {
@@ -307,11 +320,16 @@
 		0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,
 		9,9,10,10,11,11,12,12,13,13,14,14,15,15,16
 	};
-	int θ, frm;
+	int n, θ, frm;
 	Pics *pp;
 	Pic *p;
 
-	pp = &mo->o->pics[mo->state][type];
+	n = stateframe(mo);
+	if(n < 0 || n > OSend){
+		dprint("frm: %s invalid animation frame %d\n", mo->o->name, n);
+		return nil;
+	}
+	pp = &mo->o->pics[n][type];
 	if(pp->pic == nil)
 		return nil;
 	frm = pp->iscopy ? mo->freezefrm : tc % pp->nf;
--- a/fns.h
+++ b/fns.h
@@ -33,7 +33,8 @@
 int	findpath(Point, Mobj*);
 Mobj*	mapspawn(int, int, Obj*);
 void	initmap(void);
-int	spawn(int, int, Obj*, int);
+int	spawnunit(int, int, Obj*, int);
+int	spawnresource(int, int, Obj*, int);
 void	nukequeue(Pairheap**);
 Pairheap*	popqueue(Pairheap**);
 void	decreasekey(Pairheap*, double, Pairheap**);
--- a/fs.c
+++ b/fs.c
@@ -14,9 +14,11 @@
 typedef struct Tilel Tilel;
 struct Objp{
 	Obj *o;
-	int team;
+	int resource;
 	int x;
 	int y;
+	int team;
+	int amount;
 };
 struct Table{
 	char name[64];
@@ -111,10 +113,10 @@
 			memcpy(pic++, &pic0, sizeof *pic);
 			continue;
 		}
-		if(pic0.h % Nteam != 0)
+		if(pic0.h % Nplayteam != 0)
 			sysfatal("loadobjpic: obj %s sprite sheet %d,%d: height not multiple of %d",
-				pl->name, pic0.w, pic0.h, Nteam);
-		pic0.h /= Nteam;
+				pl->name, pic0.w, pic0.h, Nplayteam);
+		pic0.h /= Nplayteam;
 		n = pic0.w * pic0.h;
 		/* nteam has been set by now, no point in retaining sprites
 		 * for additional teams */
@@ -198,7 +200,7 @@
 		/* nteam isn't guaranteed to be set correctly by now, so
 		 * just set to max */
 		if(hasteam)
-			n *= Nteam;
+			n *= Nplayteam;
 		pl->p = emalloc(n * sizeof *pl->p);
 		pl->l = pic->l;
 		pic->l = pl;
@@ -349,16 +351,24 @@
 static void
 readmapobj(char **fld, int, Table *tab)
 {
+	int arg;
 	Objp *op;
 
 	if(objp == nil)
 		objp = emalloc(nobjp * sizeof *objp);
 	op = objp + tab->row;
-	unpack(fld, "oddd", &op->o, &op->team, &op->x, &op->y);
-	if(op->team > nelem(teams))
+	unpack(fld, "oddd", &op->o, &op->x, &op->y, &arg);
+	if(op->o->f & Fresource){
 		op->team = 0;
-	if(op->team > nteam)
-		nteam = op->team;
+		op->resource = 1;
+		op->amount = arg;
+	}else{
+		op->team = arg;
+		if(op->team <= 0 || op->team > nelem(teams))
+			sysfatal("readmapobj: invalid team number %d", op->team);
+		if(op->team > nteam)
+			nteam = op->team;
+	}
 }
 
 static void
@@ -398,10 +408,14 @@
 		&o->hp, &o->def, &o->vis,
 		o->cost, o->cost+1, o->cost+2, &o->time,
 		o->atk, o->atk+1, &o->speed, &o->accel, &o->halt, &o->turn);
-	o->accel /= 256.0;
-	o->halt /= 256.0;
-	/* halting distance in path node units */
-	o->halt /= Nodewidth;
+	if(o->f & Fresource)
+		o->f |= Fimmutable;
+	else{
+		o->accel /= 256.0;
+		o->halt /= 256.0;
+		/* halting distance in path node units */
+		o->halt /= Nodewidth;
+	}
 	if(o->w < 1 || o->h < 1)
 		sysfatal("readobj: %s invalid dimensions %d,%d", o->name, o->w, o->h);
 }
@@ -409,7 +423,7 @@
 static void
 readspr(char **fld, int n, Table *)
 {
-	int type, frm, nr;
+	int type, frm, nr, state;
 	Obj *o;
 	Pics *ps;
 	Pic ***ppp, **p, **pe;
@@ -419,17 +433,17 @@
 	unpack(fld, "odd", &o, &type, &nr);
 	fld += 3;
 	n -= 3;
-	ps = nil;
-	switch(type & 0xf){
-	case PFidle: ps = o->pics[OSidle]; break;
-	case PFmove: ps = o->pics[OSmove]; break;
-	default: sysfatal("readspr %s: invalid type %#02ux", o->name, type & 0x7e);
-	}
-	if(type & PFshadow)
+	state = type & PFstatemask;
+	if(state > OSend)
+		sysfatal("readspr %s: invalid state %#02ux", o->name, state);
+	ps = o->pics[state];
+	if(type & PFshadow){
 		ps += PTshadow;
-	else if(type & PFglow)
+		type |= PFimmutable;
+	}else if(type & PFglow){
 		ps += PTglow;
-	else
+		type |= PFimmutable;
+	}else
 		ps += PTbase;
 	ppp = &ps->pic;
 	if(*ppp != nil)
@@ -436,7 +450,7 @@
 		sysfatal("readspr %s: pic type %#ux already allocated", o->name, type);
 	if(ps->nf != 0 && ps->nf != n || ps->nr != 0 && ps->nr != nr)
 		sysfatal("readspr %s: spriteset phase error", o->name);
-	ps->teamcol = (type & (PFshadow|PFtile|PFglow)) == 0;
+	ps->teamcol = (type & PFimmutable) == 0;
 	ps->nf = n;
 	ps->nr = nr;
 	p = emalloc(n * sizeof *ppp);
@@ -548,14 +562,21 @@
 static void
 initmapobj(void)
 {
+	int x, y;
 	Objp *op;
 	Map *m;
 
 	for(m=map; m<map+mapwidth*mapheight; m++)
 		m->ml.l = m->ml.lp = &m->ml;
-	for(op=objp; op<objp+nobjp; op++)
-		if(spawn(op->x * Node2Tile, op->y * Node2Tile, op->o, op->team) < 0)
-			sysfatal("initmapobj: %s team %d: %r", op->o->name, op->team);
+	for(op=objp; op<objp+nobjp; op++){
+		x = op->x * Node2Tile;
+		y = op->y * Node2Tile;
+		if(op->resource){
+			if(spawnresource(x, y, op->o, op->amount) < 0)
+				sysfatal("initmapobj: %s at %d,%d: %r", op->o->name, op->x, op->y);
+		}else if(spawnunit(x, y, op->o, op->team) < 0)
+			sysfatal("initmapobj: %s team %d at %d,%d: %r", op->o->name, op->team, op->x, op->y);
+	}
 	free(objp);
 }
 
@@ -577,7 +598,7 @@
 	Pics *idle, *move;
 
 	for(o=obj; o<obj+nobj; o++){
-		if(o->f & Fbuild)
+		if(o->f & (Fbuild|Fimmutable))
 			continue;
 		idle = o->pics[OSidle];
 		move = o->pics[OSmove];
--- a/map.c
+++ b/map.c
@@ -62,7 +62,7 @@
 
 	x = *nx;
 	y = *ny;
-	if(o->f & Fbuild){
+	if(o->f & (Fbuild|Fimmutable)){
 		if(isblocked(x, y, o)){
 			werrstr("getspawn: building placement at %d,%d blocked", x, y);
 			return -1;
@@ -100,7 +100,7 @@
 {
 	Mobj *mo;
 
-	if(o->f & Fbuild && (x & Node2Tile-1 || y & Node2Tile-1)){
+	if(o->f & (Fbuild|Fimmutable) && (x & Node2Tile-1 || y & Node2Tile-1)){
 		werrstr("mapspawn: building spawn %d,%d not aligned to tile map", x, y);
 		return nil;
 	}
@@ -115,9 +115,6 @@
 	mo->subpx = mo->px << Subpxshift;
 	mo->subpy = mo->py << Subpxshift;
 	mo->o = o;
-	mo->f = o->f;
-	mo->hp = o->hp;
-	mo->θ = frand() * 256;
 	updatemap(mo);
 	return mo;
 }
--- a/sce/map1.db
+++ b/sce/map1.db
@@ -15,15 +15,16 @@
 map,7,3,22,18,8,2,16,16,12,14,5,17,23,2,14,1
 map,23,4,2,18,16,1,12,20,20,4,4,4,11,23,24,6
 map,11,15,4,9,23,17,11,8,20,19,10,7,13,14,13,10
-mapobj,control,1,2,2
-mapobj,scv,1,2,2
-mapobj,scv,1,2,2
-mapobj,scv,1,2,2
-mapobj,scv,1,2,2
-mapobj,hatchery,2,10,10
-mapobj,drone,2,10,10
-mapobj,drone,2,10,10
-mapobj,drone,2,10,10
-mapobj,drone,2,10,10
-mapobj,mutalisk,2,10,10
-mapobj,mineral0,1,8,2
+mapobj,control,2,2,1
+mapobj,scv,2,2,1
+mapobj,scv,2,2,1
+mapobj,scv,2,2,1
+mapobj,scv,2,2,1
+mapobj,hatchery,10,10,2
+mapobj,drone,10,10,2
+mapobj,drone,10,10,2
+mapobj,drone,10,10,2
+mapobj,drone,10,10,2
+mapobj,mutalisk,10,10,2
+# overloaded for now
+mapobj,mineral0,8,2,1500
--- a/sce/map2.db
+++ b/sce/map2.db
@@ -63,13 +63,13 @@
 map,7,24,10,16,13,16,22,10,20,19,24,20,15,20,20,18,18,20,5,22,19,19,16,1,15,9,1,5,18,20,10,7,18,22,15,11,3,2,4,10,19,15,7,5,6,11,22,18,22,24,14,23,1,9,3,20,12,10,19,5,4,24,21,17
 map,20,14,3,8,24,22,18,18,15,5,21,8,21,6,15,9,5,8,2,2,3,6,1,17,13,3,7,5,9,20,16,4,22,10,14,2,14,18,22,3,3,14,22,2,6,23,3,19,21,5,2,8,23,20,15,3,3,5,22,6,24,15,7,15
 map,7,18,11,4,2,5,16,2,21,19,18,3,22,18,3,20,12,14,12,10,13,18,15,6,18,16,2,16,1,11,6,24,2,15,13,6,19,17,7,16,5,12,2,16,1,22,1,6,17,1,23,13,19,21,17,19,8,19,14,15,10,5,4,1
-mapobj,control,1,3,2
-mapobj,scv,1,3,2
-mapobj,scv,1,3,2
-mapobj,scv,1,3,2
-mapobj,scv,1,3,2
-mapobj,control,2,58,59
-mapobj,scv,2,58,59
-mapobj,scv,2,58,59
-mapobj,scv,2,58,59
-mapobj,scv,2,58,59
+mapobj,control,3,2,1
+mapobj,scv,3,2,1
+mapobj,scv,3,2,1
+mapobj,scv,3,2,1
+mapobj,scv,3,2,1
+mapobj,control,58,59,2
+mapobj,scv,58,59,2
+mapobj,scv,58,59,2
+mapobj,scv,58,59,2
+mapobj,scv,58,59,2
--- a/sce/map3.db
+++ b/sce/map3.db
@@ -255,13 +255,13 @@
 map,11,13,19,8,21,1,2,14,22,15,19,3,8,18,6,7,17,11,16,12,9,15,16,16,12,20,3,9,18,3,13,13,7,12,21,3,23,24,11,4,6,9,3,6,15,12,15,3,4,17,2,3,17,23,24,15,6,19,21,18,24,4,20,21,12,24,10,2,11,10,7,13,12,24,23,24,2,24,7,21,3,11,21,13,22,5,22,8,19,12,10,4,14,6,11,16,13,21,5,17,6,21,2,2,2,22,1,4,7,24,21,2,20,13,18,16,15,24,11,2,18,23,17,9,21,4,12,22,1,15,21,17,15,16,8,4,13,20,19,23,23,5,22,17,21,1,1,11,21,18,11,19,13,21,2,6,8,23,15,16,12,3,22,13,18,16,14,4,8,3,21,12,20,11,24,13,18,23,6,10,15,11,7,1,4,24,12,2,12,7,24,13,18,8,9,10,13,20,6,8,6,10,16,2,20,16,12,1,17,2,2,5,19,15,21,12,14,9,6,22,18,7,8,21,16,23,12,9,16,20,3,22,23,8,9,10,12,10,1,12,10,24,10,9,2,23,9,11,4,4,8,13,15,4,11,17
 map,20,7,19,13,22,7,20,3,24,8,3,16,14,17,21,4,19,20,8,24,1,16,17,21,12,23,4,21,11,12,2,6,14,9,3,10,14,5,8,8,11,18,9,1,9,17,13,3,24,1,7,2,8,24,12,19,15,6,11,7,17,9,6,3,9,21,5,11,15,8,5,18,22,2,24,9,13,15,3,16,13,12,24,8,16,6,10,4,7,13,16,1,1,19,22,8,2,7,16,12,9,6,10,16,3,2,21,13,12,5,2,9,10,23,12,16,6,11,7,2,5,20,15,4,16,8,14,8,1,18,23,5,17,10,18,15,12,18,14,5,9,24,23,3,8,3,13,11,22,6,1,24,17,2,7,5,20,13,14,19,8,7,8,12,8,24,4,7,14,21,9,1,2,16,20,1,12,12,15,15,13,19,23,10,4,9,15,7,19,3,22,18,4,4,23,12,2,13,7,10,11,18,5,22,18,21,20,12,22,6,11,24,17,21,14,24,6,8,18,14,8,18,3,16,15,20,7,1,8,10,14,9,10,14,8,18,3,23,16,1,23,1,18,3,14,10,7,22,19,20,21,16,6,4,3,1
 map,8,2,22,7,11,23,9,21,7,20,13,19,1,23,10,6,19,13,13,11,10,14,22,23,5,5,18,8,3,15,21,6,23,5,24,10,7,11,11,1,5,6,14,6,11,5,3,20,24,6,9,6,2,22,3,12,18,6,18,11,2,2,16,15,3,11,3,14,8,22,13,5,21,7,11,23,10,2,19,23,11,6,12,16,24,4,20,16,12,9,20,7,22,23,20,3,2,11,12,12,22,22,4,23,13,7,10,18,15,5,7,8,8,7,1,4,18,22,6,14,14,14,21,2,8,8,21,7,23,9,24,5,7,22,22,22,11,19,22,23,21,3,21,16,18,5,22,18,7,12,8,2,8,21,11,22,24,11,17,21,8,9,6,3,11,15,12,4,3,8,12,17,15,7,19,7,12,12,3,15,6,21,13,15,18,6,21,16,17,18,6,6,6,6,1,20,21,14,22,16,21,11,3,4,15,15,22,3,14,2,3,14,1,12,17,21,4,12,24,20,12,7,3,21,7,14,16,11,8,18,10,15,2,1,12,20,17,21,1,20,6,3,20,2,11,18,7,24,14,12,12,21,13,24,7,22
-mapobj,control,1,3,3
-mapobj,scv,1,3,3
-mapobj,scv,1,3,3
-mapobj,scv,1,3,3
-mapobj,scv,1,3,3
-mapobj,control,2,249,252
-mapobj,scv,2,249,252
-mapobj,scv,2,249,252
-mapobj,scv,2,249,252
-mapobj,scv,2,249,252
+mapobj,control,3,3,1
+mapobj,scv,3,3,1
+mapobj,scv,3,3,1
+mapobj,scv,3,3,1
+mapobj,scv,3,3,1
+mapobj,control,249,252,2
+mapobj,scv,249,252,2
+mapobj,scv,249,252,2
+mapobj,scv,249,252,2
+mapobj,scv,249,252,2
--- a/sce/sce.db
+++ b/sce/sce.db
@@ -12,23 +12,33 @@
 obj,mutalisk,0x5,4,4,120,0,224,2,100,100,600,glave wurm,glave wurm,6.67,67,21745,40
 obj,control,0x8,16,12,1500,1,1,10,400,0,1800,,,0,0,0,0
 obj,hatchery,0x8,16,12,1250,1,1,10,300,0,1800,,,0,0,0,0
-obj,mineral0,0x8,8,8,1250,1,1,10,300,0,1800,,,0,0,0,0
+obj,mineral0,0x4000,8,4,,,,,,,,,,,,,
 # spawn: objname, [obj..]
 spawn,control,scv
 spawn,hatchery,drone,mutalisk
+# gather: name, resource, w, h, [richness thresholds..]
+gather,mineral0,minerals,750,500,250,1
+gather,mineral1,minerals,750,500,250,1
+gather,mineral2,minerals,750,500,250,1
 # spr: objname, flags (PF enum), rotations, [frame..]
-spr,scv,0x2,17,0
-spr,scv,0xc002,17,0
-spr,scv,0x6004,17,0,1,2,3
-spr,control,0x2,1,0
-spr,control,0xc002,1,0
-spr,drone,0x4,17,0,1,2,3,4
-spr,drone,0xc004,17,0,1,2,3,4
-spr,hatchery,2,1,0,1,2,3,3,2,1,0
-spr,hatchery,0xc002,1,0,0,0,0,0,0,0,0
-spr,mutalisk,0x2,17,0,1,2,3,4
-spr,mutalisk,0xc002,17,0,1,2,3,4
-spr,mutalisk,0x4,17,0,1,2,3,4
-spr,mutalisk,0xc004,17,0,1,2,3,4
-spr,mineral0,2,1,0
-spr,mineral0,0xc002,1,0
+spr,scv,0x0000,17,0
+spr,scv,0xc000,17,0
+spr,scv,0x6001,17,0,1,2,3
+spr,control,0x0000,1,0
+spr,control,0xc000,1,0
+spr,drone,0x0001,17,0,1,2,3,4
+spr,drone,0xc001,17,0,1,2,3,4
+spr,hatchery,0x0000,1,0,1,2,3,3,2,1,0
+spr,hatchery,0xc000,1,0,0,0,0,0,0,0,0
+spr,mutalisk,0x0000,17,0,1,2,3,4
+spr,mutalisk,0xc000,17,0,1,2,3,4
+spr,mutalisk,0x0001,17,0,1,2,3,4
+spr,mutalisk,0xc001,17,0,1,2,3,4
+spr,mineral0,0x1000,1,0
+spr,mineral0,0xd000,1,0
+spr,mineral0,0x1001,1,1
+spr,mineral0,0xd001,1,1
+spr,mineral0,0x1002,1,2
+spr,mineral0,0xd002,1,2
+spr,mineral0,0x1003,1,3
+spr,mineral0,0xd003,1,3
--- a/sim.c
+++ b/sim.c
@@ -40,7 +40,7 @@
 	Map *m;
 
 	m = map + mo->y / Node2Tile * mapwidth + mo->x / Node2Tile;
-	mo->mobjl = linkmobj(mo->f & Fair ? m->ml.lp : &m->ml, mo, mo->mobjl);
+	mo->mobjl = linkmobj(mo->o->f & Fair ? m->ml.lp : &m->ml, mo, mo->mobjl);
 }
 
 static void
@@ -50,7 +50,7 @@
 	Team *t;
 
 	t = teams + mo->team;
-	if(mo->f & Fbuild)
+	if(mo->o->f & (Fbuild|Fimmutable))
 		t->nbuild++;
 	else
 		t->nunit++;
@@ -143,10 +143,6 @@
 int
 moveone(Point p, Mobj *mo, Mobj *block)
 {
-	if(mo->o->speed == 0){
-		dprint("move: obj %s can't move\n", mo->o->name);
-		return -1;
-	}
 	setgoal(&p, mo, block);
 	if(repath(p, mo) < 0){
 		mo->speed = 0.0;
@@ -157,14 +153,30 @@
 }
 
 int
-spawn(int x, int y, Obj *o, int n)
+spawnunit(int x, int y, Obj *o, int team)
 {
 	Mobj *mo;
 
 	if((mo = mapspawn(x, y, o)) == nil)
 		return -1;
-	mo->team = n;
+	mo->team = team;
+	mo->θ = frand() * 256;
+	mo->hp = o->hp;
 	mo->state = OSidle;
+	refmobj(mo);
+	return 0;
+}
+
+int
+spawnresource(int x, int y, Obj *o, int amount)
+{
+	Mobj *mo;
+
+	if((mo = mapspawn(x, y, o)) == nil)
+		return -1;
+	mo->state = -1;
+	mo->team = 0;
+	mo->amount = amount;
 	refmobj(mo);
 	return 0;
 }