shithub: sce

Download patch

ref: 45e74ee185103abd827298d0e9b264811d60f0e7
parent: 0622cb8e9d9874ee52e9881415554bc38570551d
author: qwx <qwx@sciops.net>
date: Thu Aug 5 14:08:17 EDT 2021

sim: generalize moving into a queuable action

player issues commands, each command is essentially a script
with a hardcoded sequence of actions, and is pushed to a
mobj's command queue.
practically all commands begin by moving the unit somewhere.
the command stores goal map coordinates and unit id's to avoid
races; actions then decide how to use these.
once an action sequence completes, the command is popped
from the queue and the next one is executed (untested until
move waypoints are added).
a new transient mobj state is set between transitions.
also, look ma: oop!

this essentially implements a shitty way to script behavior...
pending better solution.
sim.c is split by action/state so as to be more manageable.
some refactoring to make function names more expressive.

--- a/com.c
+++ b/com.c
@@ -59,30 +59,17 @@
 }
 
 static Mobj *
-getmobj(Mobj *r)
+mobjfromreq(Mobj *r)
 {
-	int n;
 	Mobj *mo;
-	Team *t;
 
-	n = r->idx >> Teamshift & Nteam - 1;
-	if(n < 0 || n > nteam){
-		werrstr("invalid team number %d", n);
+	if((mo = derefmobj(r->idx, r->uuid)) == nil)
 		return nil;
-	}
-	t = teams + n;
-	n = r->idx & Teamidxmask;
-	if(n > t->sz || (mo = t->mo[n]) == nil){
-		werrstr("obj index %d out of bounds", n);
+	if(mo->x != r->x || mo->y != r->y){
+		werrstr("phase error: req mobj at %d,%d, found %s at %d,%d",
+			r->x, r->y, mo->o->name, mo->x, mo->y);
 		return nil;
 	}
-	if(mo->idx != r->idx || mo->uuid != r->uuid
-	|| mo->x != r->x || mo->y != r->y){
-		werrstr("phase error: %s at %d,%d has %#ux,%ld, req has %d,%d %#ux,%ld",
-			mo->o->name, mo->x, mo->y, mo->idx, mo->uuid,
-			r->x, r->y, r->idx, r->uuid);
-		return nil;
-	}
 	return mo;
 }
 
@@ -98,19 +85,21 @@
 	&click.x, &click.y,
 	&reqt.idx, &reqt.uuid, &reqt.x, &reqt.y)) < 0)
 		return -1;
-	if((mo = getmobj(&reqm)) == nil)
+	if((mo = mobjfromreq(&reqm)) == nil)
 		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)
+	if((tgt = mobjfromreq(&reqt)) == nil)
 		return -1;
 	if(click.x >= nodemapwidth || click.y >= nodemapheight){
 		werrstr("reqmovenear: invalid location %d,%d", click.x, click.y);
 		return -1;
 	}
-	moveone(click, mo, tgt);
+	clearcommands(mo);
+	if(pushmovecommand(click, mo, tgt) < 0)
+		return -1;
 	return n;
 }
 
@@ -125,7 +114,7 @@
 	&reqm.idx, &reqm.uuid, &reqm.x, &reqm.y,
 	&tgt.x, &tgt.y)) < 0)
 		return -1;
-	if((mo = getmobj(&reqm)) == nil)
+	if((mo = mobjfromreq(&reqm)) == nil)
 		return -1;
 	if((mo->o->f & Fimmutable) || mo->o->speed == 0.0){
 		werrstr("reqmove: object %s can't move", mo->o->name);
@@ -135,7 +124,9 @@
 		werrstr("reqmove: invalid target %d,%d", tgt.x, tgt.y);
 		return -1;
 	}
-	moveone(tgt, mo, nil);
+	clearcommands(mo);
+	if(pushmovecommand(tgt, mo, nil) < 0)
+		return -1;
 	return n;
 }
 
--- a/dat.h
+++ b/dat.h
@@ -5,6 +5,8 @@
 typedef struct Pics Pics;
 typedef struct Obj Obj;
 typedef struct Path Path;
+typedef struct Action Action;
+typedef struct Command Command;
 typedef struct Munit Munit;
 typedef struct Mresource Mresource;
 typedef struct Mobj Mobj;
@@ -108,6 +110,7 @@
 	OState2,
 	OState3,
  	OSend,
+ 	OSskymaybe = 666,
 
 	/* unit */
 	OSidle = OState0,
@@ -134,6 +137,8 @@
 	PFalpha = 1<<14,
 	PFshadow = 1<<15,
 	PFtile = 1<<16,
+
+	Ncmd = 32,
 };
 struct Obj{
 	char *name;
@@ -169,7 +174,6 @@
 	int amount;
 };
 struct Munit{
-	int state;
 	int team;
 	int hp;
 	int xp;
@@ -181,13 +185,30 @@
 	double u;
 	double v;
 	double speed;
-	Mobjl *movingp;
-	Mobjl *mapp;
+	Mobjl *mobjl;
+	Mobjl *mapl;
 };
+struct Command{
+	int os;
+	char *name;
+	int (*initfn)(Mobj*);
+	Point goal;
+	vlong arg[4];
+};
+struct Action{
+	int os;
+	char *name;
+	void (*stepfn)(Mobj*);
+	void (*cleanupfn)(Mobj*);
+};
 struct Mobj{
 	Obj *o;
 	int idx;
 	long uuid;
+	int state;
+	Action *actp;
+	Command cmds[Ncmd];
+	int ctail;
 	Point;
 	int px;
 	int py;
--- a/fns.h
+++ b/fns.h
@@ -8,11 +8,20 @@
 int	sendpause(void);
 void	stepsnd(void);
 void	initsnd(void);
-void	updatemoves(void);
 void	linktomap(Mobj*);
-int	moveone(Point, Mobj*, Mobj*);
+int	pushmovecommand(Point, Mobj*, Mobj*);
+void	resourcestate(Mobj*);
+void	idlestate(Mobj*);
+Mobj*	derefmobj(int, long);
+void	nextaction(Mobj*);
+int	pushactions(Mobj*, Action*);
+void	clearcommands(Mobj*);
+void	abortcommands(Mobj*);
+void	popcommand(Mobj*);
+Command*	pushcommand(Mobj*);
 Mobjl*	linkmobj(Mobjl*, Mobj*, Mobjl*);
 void	unlinkmobj(Mobjl*);
+void	refmobj(Mobj*);
 void	stepsim(void);
 void	initsim(void);
 void	initsv(int, char*);
@@ -36,6 +45,7 @@
 int	findpath(Point, Mobj*);
 Mobj*	mapspawn(int, int, Obj*);
 void	initmap(void);
+Mobj*	derefmobj(int, long);
 int	spawnunit(int, int, Obj*, int);
 int	spawnresource(int, int, Obj*, int);
 void	nukequeue(Pairheap**);
--- a/mkfile
+++ b/mkfile
@@ -12,7 +12,10 @@
 	pheap.$O\
 	sce.$O\
 	sim.$O\
+	sim.idle.$O\
 	sim.move.$O\
+	sim.resource.$O\
+	sim.spawn.$O\
 	snd.$O\
 	sv.$O\
 	util.$O\
--- a/sim.c
+++ b/sim.c
@@ -8,6 +8,8 @@
 int nteam;
 int initres[Nresource], foodcap;
 
+static Mobjl mobjl0 = {.l = &mobjl0, .lp = &mobjl0}, *mobjl = &mobjl0;
+
 Mobjl *
 linkmobj(Mobjl *l, Mobj *mo, Mobjl *p)
 {
@@ -31,12 +33,13 @@
 	ml->lp = ml->l = nil;
 }
 
-static void
+void
 refmobj(Mobj *mo)
 {
 	int n, i;
 	Team *t;
 
+	mo->mobjl = linkmobj(mobjl, mo, nil);
 	t = teams + mo->team;
 	if(mo->o->f & (Fbuild|Fimmutable))
 		t->nbuild++;
@@ -55,55 +58,104 @@
 	t->firstempty = i;
 }
 
-int
-spawnunit(int x, int y, Obj *o, int team)
+void
+nextaction(Mobj *mo)
 {
-	Mobj *mo;
+	assert(mo->actp != nil);
+	if(mo->actp->cleanupfn != nil)
+		mo->actp->cleanupfn(mo);
+	mo->actp++;
+	if((mo->state = mo->actp->os) == OSskymaybe){
+		dprint("A nextaction %s %#p: done\n", mo->o->name, mo);
+		mo->actp = nil;
+		popcommand(mo);
+		return;
+	}
+	dprint("A nextaction %s %#p: %s\n", mo->o->name, mo, mo->actp->name);
+}
 
-	if((mo = mapspawn(x, y, o)) == nil)
-		return -1;
-	mo->team = team;
-	mo->θ = frand() * 256;
-	mo->hp = o->hp;
-	mo->state = OSidle;
-	refmobj(mo);
+int
+pushactions(Mobj *mo, Action *a)
+{
+	mo->actp = a;
+	mo->state = a->os;
+	dprint("A pushaction %s %#p: %s\n", mo->o->name, mo, a->name);
 	return 0;
 }
 
-int
-spawnresource(int x, int y, Obj *o, int amount)
+void
+clearcommands(Mobj *mo)
 {
-	int *t, *te;
+	dprint("C clearcommand %s %#p: %s\n", mo->o->name, mo, mo->cmds[0].name);
+	if(mo->actp != nil && mo->actp->cleanupfn != nil)
+		mo->actp->cleanupfn(mo);
+	mo->actp = nil;
+	memset(mo->cmds, 0, sizeof mo->cmds);
+	mo->ctail = 0;
+	idlestate(mo);
+}
+
+void
+abortcommands(Mobj *mo)
+{
+	dprint("C abortcommand %s %#p: %s\n", mo->o->name, mo, mo->cmds[0].name);
+	clearcommands(mo);
+}
+
+void
+popcommand(Mobj *mo)
+{
+	dprint("C popcommand %s %#p: %s\n", mo->o->name, mo, mo->cmds[0].name);
+	if(--mo->ctail > 0){
+		memmove(mo->cmds, mo->cmds+1, mo->ctail * sizeof *mo->cmds);
+		mo->state = OSskymaybe;
+	}else
+		clearcommands(mo);
+}
+
+Command *
+pushcommand(Mobj *mo)
+{
+	Command *c;
+
+	fprint(2, "pushcommand %s %#p\n", mo->o->name, mo);
+	if(mo->ctail >= nelem(mo->cmds)){
+		werrstr("command buffer overflow");
+		return nil;
+	}
+	c = mo->cmds + mo->ctail++;
+	if(mo->state == OSidle)
+		mo->state = OSskymaybe;
+	return c;
+}
+
+static void
+updatemobj(void)
+{
+	Mobjl *ml, *next;
 	Mobj *mo;
-	Resource *r;
 
-	if(amount <= 0){
-		werrstr("spawnresource: invalid amount");
-		return -1;
+	for(ml=mobjl->l, next=ml->l; ml!=mobjl; ml=next, next=next->l){
+		mo = ml->mo;
+		if(mo->state == OSidle)
+			continue;
+		if(mo->actp == nil
+		&& (mo->cmds[0].initfn(mo) < 0 || mo->actp == nil || mo->state == OSskymaybe)){
+			/* FIXME: always skymaybe */
+			abortcommands(mo);
+			continue;
+		}
+		if(mo->state == OSskymaybe)
+			sysfatal("updatemobj: %s cmd %s impossible/stale state %d",
+				mo->o->name, mo->cmds[0].name, mo->state);
+		mo->actp->stepfn(mo);
 	}
-	if((mo = mapspawn(x, y, o)) == nil)
-		return -1;
-	mo->team = 0;
-	mo->amount = amount;
-	mo->state = OSrich;
-	r = o->res;
-	for(t=r->thresh, te=t+r->nthresh; t<te; t++){
-		if(amount >= *t)
-			break;
-		mo->state++;
-	}
-	if(mo->state >= OSend){
-		dprint("spawnresource %s %d,%d: invalid state %d\n", o->name, x, y, mo->state);
-		mo->state = OSpoor;
-	}
-	refmobj(mo);
-	return 0;
 }
 
 void
 stepsim(void)
 {
-	updatemoves();
+	updatemobj();
 }
 
 void
--- /dev/null
+++ b/sim.idle.c
@@ -1,0 +1,29 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include "dat.h"
+#include "fns.h"
+
+static void
+freezefrm(Mobj *mo, int oldstate)
+{
+	Pics *old, *new;
+
+	old = mo->o->pics[oldstate];
+	new = mo->o->pics[OSidle];
+	if(!new->freeze || !old->shared){
+		mo->freezefrm = 0;
+		return;
+	}
+	mo->freezefrm = tc % old[PTbase].nf;
+	if(mo->freezefrm > new[PTbase].nf)
+		sysfatal("idle:freezefrm obj %s: invalid frame number %d > %d",
+			mo->o->name, mo->freezefrm, new[PTbase].nf);
+}
+
+void
+idlestate(Mobj *mo)
+{
+	freezefrm(mo, mo->state);
+	mo->state = OSidle;
+}
--- a/sim.move.c
+++ b/sim.move.c
@@ -4,8 +4,6 @@
 #include "dat.h"
 #include "fns.h"
 
-static Mobjl moving0 = {.l = &moving0, .lp = &moving0}, *moving = &moving0;
-
 void
 linktomap(Mobj *mo)
 {
@@ -12,7 +10,7 @@
 	Map *m;
 
 	m = map + mo->y / Node2Tile * mapwidth + mo->x / Node2Tile;
-	mo->mapp = linkmobj(mo->o->f & Fair ? m->ml.lp : &m->ml, mo, mo->mapp);
+	mo->mapl = linkmobj(mo->o->f & Fair ? m->ml.lp : &m->ml, mo, mo->mapl);
 }
 
 static void
@@ -25,7 +23,7 @@
 }
 
 static double
-facemobj(Point p, Mobj *mo)
+facegoal(Point p, Mobj *mo)
 {
 	int dx, dy;
 	double vx, vy, d, θ, θ256, Δθ;
@@ -56,83 +54,60 @@
 }
 
 static void
-freemove(Mobj *mo)
+nextpathnode(Mobj *mo)
 {
-	Pics *old, *new;
+	resetcoords(mo);
+	facegoal(*mo->pathp, mo);
+}
 
-	unlinkmobj(mo->movingp);
+static void
+clearpath(Mobj *mo)
+{
+	mo->speed = 0.0;
 	mo->pathp = nil;
-	old = mo->o->pics[mo->state];
-	new = mo->o->pics[OSidle];
-	if(new->freeze && old->shared){
-		mo->freezefrm = tc % old[PTbase].nf;
-		if(mo->freezefrm > new[PTbase].nf)
-			sysfatal("freemove obj %s: invalid frame number %d > %d",
-				mo->o->name, mo->freezefrm, new[PTbase].nf);
-	}else
-		mo->freezefrm = 0;
-	mo->state = OSidle;
 	resetcoords(mo);
 }
 
 static void
-nextmove(Mobj *mo)
+cleanup(Mobj *mo)
 {
-	resetcoords(mo);
-	facemobj(*mo->pathp, mo);
+	clearpath(mo);
+	mo->target = (Point){0,0};
+	mo->goalblocked = 0;
+	mo->pathlen = 0.0;
+	mo->npatherr = 0;
 }
 
+static void
+movedone(Mobj *mo)
+{
+	dprint("mobj %s %#p successfully reached goal\n", mo->o->name, mo);
+	nextaction(mo);
+}
+
+static void
+abortmove(Mobj *mo)
+{
+	werrstr("move aborted");
+	abortcommands(mo);
+}
+
 static int
 repath(Point p, Mobj *mo)
 {
-	freemove(mo);
+	clearpath(mo);
 	mo->target = p;
 	if(findpath(p, mo) < 0){
-		mo->θ = facemobj(p, mo);
+		mo->θ = facegoal(p, mo);
 		return -1;
 	}
-	mo->movingp = linkmobj(moving, mo, mo->movingp);
 	mo->pathp = mo->paths;
-	mo->state = OSmove;
-	nextmove(mo);
+	nextpathnode(mo);
 	return 0;
 }
 
-int
-moveone(Point p, Mobj *mo, Mobj *block)
-{
-	setgoal(&p, mo, block);
-	if(repath(p, mo) < 0){
-		mo->speed = 0.0;
-		dprint("move to %d,%d: %r\n", p.x, p.y);
-		return -1;
-	}
-	return 0;
-}
-
-static int
-tryturn(Mobj *mo)
-{
-	int r;
-	double Δθ;
-
-	r = 1;
-	if(mo->Δθ <= mo->o->turn){
-		r = 0;
-		Δθ = mo->Δθ;
-	}else
-		Δθ = mo->o->turn;
-	mo->θ += mo->Δθs * Δθ;
-	if(mo->θ < 0)
-		mo->θ += 256;
-	else if(mo->θ >= 256)
-		mo->θ -= 256;
-	mo->Δθ -= Δθ;
-	return r;
-}
-
 static void
-updatespeed(Mobj *mo)
+accelerate(Mobj *mo)
 {
 	if(1 + mo->pathlen < (mo->speed / 8) * (mo->speed / 8) / 2 / (mo->o->accel / 8)){
 		mo->speed -= mo->o->accel;
@@ -224,78 +199,143 @@
 }
 
 static int
-domove(Mobj *mo)
+continuemove(Mobj *mo)
 {
 	int r;
 
-	updatespeed(mo);
-	unlinkmobj(mo->mapp);
+	accelerate(mo);
+	unlinkmobj(mo->mapl);
 	r = trymove(mo);
 	linktomap(mo);
 	return r;
 }
 
+static int
+tryturn(Mobj *mo)
+{
+	int r;
+	double Δθ;
+
+	r = 1;
+	if(mo->Δθ <= mo->o->turn){
+		r = 0;
+		Δθ = mo->Δθ;
+	}else
+		Δθ = mo->o->turn;
+	mo->θ += mo->Δθs * Δθ;
+	if(mo->θ < 0)
+		mo->θ += 256;
+	else if(mo->θ >= 256)
+		mo->θ -= 256;
+	mo->Δθ -= Δθ;
+	return r;
+}
+
+static int
+nodereached(Mobj *mo)
+{
+	return mo->px == mo->pathp->x && mo->py == mo->pathp->y;
+}
+
 static void
-stepmove(Mobj *mo)
+step(Mobj *mo)
 {
-	int n;
+	int nerr;
 
-	n = 0;
+	nerr = 0;
 restart:
-	n++;
 	if(tryturn(mo))
 		return;
-	if(domove(mo) < 0){
-		if(n > 1){
-			fprint(2, "stepmove: %s %#p bug inducing infinite loop!\n",
-				mo->o->name, mo);
+	if(continuemove(mo) < 0){
+		if(nerr > 1){
+			fprint(2, "stepmove: %s %#p bug: infinite loop!\n", mo->o->name, mo);
 			return;
 		}
-		dprint("stepmove: failed to move: %r\n");
+		dprint("stepmove: %s %#p failed moving to %d,%d from %d,%d: %r\n",
+			mo->o->name, mo, mo->pathp->x, mo->pathp->y, mo->px, mo->py);
 		if(repath(mo->target, mo) < 0){
-			dprint("stepmove: %s %#p moving towards target: %r\n",
-				mo->o->name, mo);
-			mo->speed = 0.0;
+			dprint("stepmove: %s %#p moving towards target: %r\n", mo->o->name, mo);
+			abortcommands(mo);
 			return;
 		}
+		nerr++;
 		goto restart;
 	}
-	if(mo->px == mo->pathp->x && mo->py == mo->pathp->y){
-		mo->pathp++;
-		if(mo->pathp < mo->pathe){
-			nextmove(mo);
-			return;
-		}else if(mo->x == mo->target.x && mo->y == mo->target.y){
-			mo->npatherr = 0;
-			mo->speed = 0.0;
-			freemove(mo);
-			return;
-		}
-		dprint("stepmove: %s %#p reached final node, but not target\n",
+	if(!nodereached(mo))
+		return;
+	mo->pathp++;
+	if(mo->pathp < mo->pathe){
+		nextpathnode(mo);
+		return;
+	}else if(mo->x == mo->target.x && mo->y == mo->target.y){
+		movedone(mo);
+		return;
+	}
+	dprint("stepmove: %s %#p reached final node, but not target\n",
+		mo->o->name, mo);
+	if(mo->goalblocked && isblocked(mo->target.x, mo->target.y, mo->o)){
+		dprint("stepmove: %s %#p goal still blocked, stopping\n", mo->o->name, mo);
+		abortmove(mo);
+		return;
+	}
+	dprint("stepmove: %s %#p trying again\n", mo->o->name, mo);
+	if(mo->npatherr++ > 1 || repath(mo->target, mo) < 0){
+		dprint("stepmove: %s %#p still can't reach target: %r\n",
 			mo->o->name, mo);
-		if(mo->goalblocked && isblocked(mo->target.x, mo->target.y, mo->o)){
-			dprint("stepmove: %s %#p goal still blocked, stopping\n",
-				mo->o->name, mo);
-			mo->speed = 0.0;
-			freemove(mo);
-			return;
-		}
-		if(mo->npatherr++ > 1
-		|| repath(mo->target, mo) < 0){
-			dprint("stepmove: %s %#p trying to find target: %r\n",
-				mo->o->name, mo);
-			mo->npatherr = 0;
-			mo->speed = 0.0;
-			freemove(mo);
-		}
+		abortmove(mo);
+		return;
 	}
 }
 
-void
-updatemoves(void)
+static Action acts[] = {
+	{
+		.os = OSmove,
+		.name = "moving",
+		.stepfn = step,
+		.cleanupfn = cleanup,
+	},
+	{
+		.os = OSskymaybe,
+	}
+};
+
+int
+newmove(Mobj *mo)
 {
-	Mobjl *ml, *oml;
+	Point goal;
+	Mobj *block;
+	Command *c;
 
-	for(oml=moving->l, ml=oml->l; oml!=moving; oml=ml, ml=ml->l)
-		stepmove(oml->mo);
+	c = mo->cmds;
+	goal = c->goal;
+	block = nil;
+	if(c->arg[0] >= 0 && (block = derefmobj(c->arg[0], c->arg[1])) == nil)
+		return -1;
+	setgoal(&goal, mo, block);
+	if(repath(goal, mo) < 0)
+		return -1;
+	if(pushactions(mo, acts) < 0)
+		return -1;
+	return 0;
+}
+
+int
+pushmovecommand(Point goal, Mobj *mo, Mobj *block)
+{
+	Command *c;
+
+	if((c = pushcommand(mo)) == nil){
+		fprint(2, "pushmovecommand: %r\n");
+		return -1;
+	}
+	c->os = OSmove;
+	c->name = "move";
+	c->initfn = newmove;
+	c->goal = goal;
+	if(block != nil){
+		c->arg[0] = block->idx;
+		c->arg[1] = block->uuid;
+	}else
+		c->arg[0] = -1;
+	return 0;
 }
--- /dev/null
+++ b/sim.resource.c
@@ -1,0 +1,18 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include "dat.h"
+#include "fns.h"
+
+void
+resourcestate(Mobj *mo)
+{
+	int os, *t, *te;
+	Resource *r;
+
+	r = mo->o->res;
+	for(os=OSrich, t=r->thresh, te=t+r->nthresh; t<te; t++, os++)
+		if(mo->amount >= *t)
+			break;
+	mo->state = os;
+}
--- /dev/null
+++ b/sim.spawn.c
@@ -1,0 +1,64 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include "dat.h"
+#include "fns.h"
+
+Mobj *
+derefmobj(int idx, long uuid)
+{
+	int n;
+	Mobj *mo;
+	Team *t;
+
+	n = idx >> Teamshift & Nteam - 1;
+	if(n < 0 || n > nteam){
+		werrstr("invalid team number %d", n);
+		return nil;
+	}
+	t = teams + n;
+	n = idx & Teamidxmask;
+	if(n > t->sz || (mo = t->mo[n]) == nil){
+		werrstr("mobj index %d out of bounds or missing", n);
+		return nil;
+	}
+	if(mo->idx != idx || mo->uuid != uuid){
+		werrstr("phase error: %#ux,%ld → %s %#ux,%ld",
+			idx, uuid, mo->o->name, mo->idx, mo->uuid);
+		return nil;
+	}
+	return mo;
+}
+
+int
+spawnunit(int x, int y, Obj *o, int team)
+{
+	Mobj *mo;
+
+	if((mo = mapspawn(x, y, o)) == nil)
+		return -1;
+	mo->team = team;
+	mo->θ = frand() * 256;
+	mo->hp = o->hp;
+	idlestate(mo);
+	refmobj(mo);
+	return 0;
+}
+
+int
+spawnresource(int x, int y, Obj *o, int amount)
+{
+	Mobj *mo;
+
+	if(amount <= 0){
+		werrstr("spawnresource: invalid amount");
+		return -1;
+	}
+	if((mo = mapspawn(x, y, o)) == nil)
+		return -1;
+	mo->team = 0;
+	mo->amount = amount;
+	resourcestate(mo);
+	refmobj(mo);
+	return 0;
+}