/*
Copyright (C) 2011 Id Software, Inc.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/
/*
this file deals with qc builtins to apply custom skeletal blending (skeletal objects extension), as well as the logic required to perform realtime ragdoll, if I ever implement that.
*/

/*
skeletal objects are just a set of bone poses.
they are separate from the entity they are attached to, and must be created/destroyed separately.
typically the bones are all stored relative to their parent.
qc must use skel_build to copy animation data from the file format into the skeletal object for rendering, but can build different bones using different animations or can override explicit bones.

ragdoll file is a description of the joints in the ragdoll.
a skeletal object, built from a doll instead of a model, has a series of physics objects created at key points (ie: not face).
these objects are absolute
qc must build the skeletal object still, which fills the skeletal object from the physics objects instead of animation data, for bones that have solid objects.
*/

#include "quakedef.h"

#if defined(RAGDOLL) || defined(SKELETALOBJECTS)

#include "pr_common.h"
#include "com_mesh.h"

#define MAX_SKEL_OBJECTS 1024

#ifdef RAGDOLL
/*this is the description of the ragdoll, it is how the doll flops around*/
typedef struct doll_s
{
	char *name;
	int uses;
	model_t *model;
	struct doll_s *next;

	qboolean drawn:1;
	int numdefaultanimated;
	int numbodies;
	int numjoints;
	int numbones;
	odebodyinfo_t *body;
	odejointinfo_t *joint;
	struct
	{
		//easy lookup table for bone->body.
		//most of these will be -1, which means 'import from animation object'
		int bodyidx;
	} *bone;
} doll_t;

typedef struct
{
	odebody_t odebody;

//	int ownerent;	/*multiple of 12*/
//	int flags;

//	float moment[12];
	float animstrength;
	float animmatrix[12];
} body_t;
#endif

/*this is the skeletal object*/
typedef struct skelobject_s
{
	int inuse;

	model_t *model;
	world_t *world; /*be it ssqc or csqc*/
	enum
	{
		SKOT_RELATIVE,	//relative to parent
		SKOT_ABSOLUTE	//relative to model
	} type;

	unsigned int numbones;
	float *bonematrix;

#ifdef RAGDOLL
	int numanimated;
	struct skelobject_s *animsource;
	unsigned int numbodies;
	body_t *body;
	int numjoints;
	odejoint_t *joint;
	doll_t *doll;
	wedict_t *entity;	//only valid for dolls.
#endif
} skelobject_t;

#ifdef RAGDOLL
static doll_t *dolllist;
#endif
static skelobject_t skelobjects[MAX_SKEL_OBJECTS];
static int numskelobjectsused;

//copes with src==dst to convert rel->abs
void skel_copy_toabs(skelobject_t *skelobjdst, skelobject_t *skelobjsrc, int startbone, int endbone)
{
	galiasbone_t *boneinfo = Mod_GetBoneInfo(skelobjsrc->model);
	if (!boneinfo)
		return;
	if (skelobjsrc->type == SKOT_ABSOLUTE)
	{
		if (skelobjsrc != skelobjdst)
		{
			while(startbone < endbone)
			{
				Vector4Copy(skelobjsrc->bonematrix+12*startbone+0, skelobjdst->bonematrix+12*startbone+0);
				Vector4Copy(skelobjsrc->bonematrix+12*startbone+4, skelobjdst->bonematrix+12*startbone+4);
				Vector4Copy(skelobjsrc->bonematrix+12*startbone+8, skelobjdst->bonematrix+12*startbone+8);
				startbone++;
			}
		}
	}
	else
	{
		if (skelobjsrc != skelobjdst)
		{
			while(startbone < endbone)
			{
				if (boneinfo[startbone].parent >= 0)
				{
					Matrix3x4_Multiply(skelobjsrc->bonematrix+12*startbone, skelobjdst->bonematrix+12*boneinfo[startbone].parent, skelobjdst->bonematrix+12*startbone);
				}
				else
				{
					Vector4Copy(skelobjsrc->bonematrix+12*startbone+0, skelobjdst->bonematrix+12*startbone+0);
					Vector4Copy(skelobjsrc->bonematrix+12*startbone+4, skelobjdst->bonematrix+12*startbone+4);
					Vector4Copy(skelobjsrc->bonematrix+12*startbone+8, skelobjdst->bonematrix+12*startbone+8);
				}
				startbone++;
			}
		}
		else
		{
			float tmpmat[12];
			while(startbone < endbone)
			{
				if (boneinfo[startbone].parent >= 0)
				{
					Matrix3x4_Multiply(skelobjsrc->bonematrix+12*startbone, skelobjdst->bonematrix+12*boneinfo[startbone].parent, tmpmat);
					memcpy(skelobjdst->bonematrix+12*startbone, tmpmat, sizeof(tmpmat));
				}
				startbone++;
			}
		}
	}

	skelobjdst->type = SKOT_ABSOLUTE;
}
static void bonemat_fromidentity(float *out)
{
	out[0] = 1;
	out[1] = 0;
	out[2] = 0;
	out[3] = 0;

	out[4] = 0;
	out[5] = 1;
	out[6] = 0;
	out[7] = 0;

	out[8] = 0;
	out[9] = 0;
	out[10] = 1;
	out[11] = 0;
}
static void bonemat_fromqcvectors(float *out, const float vx[3], const float vy[3], const float vz[3], const float t[3])
{
	out[0] = vx[0];
	out[1] = -vy[0];
	out[2] = vz[0];
	out[3] = t[0];
	out[4] = vx[1];
	out[5] = -vy[1];
	out[6] = vz[1];
	out[7] = t[1];
	out[8] = vx[2];
	out[9] = -vy[2];
	out[10] = vz[2];
	out[11] = t[2];
}
static void bonemat_fromaxisorg(float *out, vec3_t axis[3], const float t[3])
{
	out[0] = axis[0][0];
	out[1] = axis[1][0];
	out[2] = axis[2][0];
	out[3] = t[0];
	out[4] = axis[0][1];
	out[5] = axis[1][1];
	out[6] = axis[2][1];
	out[7] = t[1];
	out[8] = axis[0][2];
	out[9] = axis[1][2];
	out[10]= axis[2][2];
	out[11]= t[2];
}
static void bonemat_fromentity(world_t *w, wedict_t *ed, float *trans)
{
	vec3_t d[3], a;
	model_t *mod;
	mod = w->Get_CModel(w, ed->v->modelindex);
	if (!mod || mod->type == mod_alias)
		a[0] = -ed->v->angles[0];
	else
		a[0] = ed->v->angles[0];
	a[1] = ed->v->angles[1];
	a[2] = ed->v->angles[2];
	AngleVectors(a, d[0], d[1], d[2]);
	bonemat_fromqcvectors(trans, d[0], d[1], d[2], ed->v->origin);
}
static void bonemat_toqcvectors(const float *in, float vx[3], float vy[3], float vz[3], float t[3])
{
	vx[0] = in[0];
	vx[1] = in[4];
	vx[2] = in[8];
	vy[0] = -in[1];
	vy[1] = -in[5];
	vy[2] = -in[9];
	vz[0] = in[2];
	vz[1] = in[6];
	vz[2] = in[10];
	t [0] = in[3];
	t [1] = in[7];
	t [2] = in[11];
}

static void bonematident_toqcvectors(float vx[3], float vy[3], float vz[3], float t[3])
{
	vx[0] = 1;
	vx[1] = 0;
	vx[2] = 0;
	vy[0] = -0;
	vy[1] = -1;
	vy[2] = -0;
	vz[0] = 0;
	vz[1] = 0;
	vz[2] = 1;
	t [0] = 0;
	t [1] = 0;
	t [2] = 0;
}


static qboolean pendingkill; /*states that there is a skel waiting to be killed*/
#ifdef RAGDOLL
void rag_uninstanciate(skelobject_t *sko);
int rag_finddollbody(doll_t *d, char *bodyname)
{
	int i;
	for (i = 0; i < d->numbodies; i++)
	{
		if (!strcmp(d->body[i].name, bodyname))
			return i;
	}
	return -1;
}
int rag_finddolljoint(doll_t *d, char *name)
{
	int i;
	for (i = 0; i < d->numjoints; i++)
	{
		if (!strcmp(d->joint[i].name, name))
			return i;
	}
	return -1;
}

typedef struct {
	qboolean errors;
	doll_t *d;
	galiasbone_t *bones;

	odebodyinfo_t *body;
	odejointinfo_t *joint;

	odebodyinfo_t defbody;
	odejointinfo_t defjoint;
} dollcreatectx_t;
dollcreatectx_t *rag_createdoll(model_t *mod, char *fname, int numbones)
{
	int i;
	dollcreatectx_t *ctx;
	int nummodbones = Mod_GetNumBones(mod, false);
	if (nummodbones != numbones || !numbones)
		return NULL;

	ctx = malloc(sizeof(*ctx));

	ctx->bones = Mod_GetBoneInfo(mod);
	ctx->errors = 0;

	memset(&ctx->defbody, 0, sizeof(ctx->defbody));
	ctx->defbody.animate = true;
	ctx->defbody.shape = SOLID_PHYSICS_BOX;
	ctx->defbody.dimensions[0] = 4;
	ctx->defbody.dimensions[1] = 4;
	ctx->defbody.dimensions[2] = 4;
	ctx->defbody.mass = 1;
	ctx->defbody.orient = false;

	memset(&ctx->defjoint, 0, sizeof(ctx->defjoint));
	ctx->defjoint.axis[1] = 1;
	ctx->defjoint.axis2[2] = 1;
	ctx->defjoint.ERP = 0.2;	//use ODE defaults
	ctx->defjoint.ERP2 = 0.2;
	ctx->defjoint.CFM = 1e-5;
	ctx->defjoint.CFM2 = 1e-5;

	ctx->d = BZ_Malloc(sizeof(*ctx->d));
	ctx->d->next = dolllist;
	ctx->d->name = strdup(fname);
	ctx->d->model = mod;
	ctx->d->numbodies = 0;
	ctx->d->body = NULL;
	ctx->d->numjoints = 0;
	ctx->d->uses = 0;
	ctx->d->joint = NULL;

	ctx->d->numbones = numbones;
	ctx->d->bone = BZ_Malloc(sizeof(*ctx->d->bone) * ctx->d->numbones);
	for (i = 0; i < ctx->d->numbones; i++)
		ctx->d->bone[i].bodyidx = -1;

	return ctx;
}
//returns true if the command was recognised. false if the command is for something else.
qboolean rag_dollline(dollcreatectx_t *ctx, int linenum)
{
	int i;
	int argc;
	char *cmd, *val;
	doll_t *d = ctx->d;

	argc = Cmd_Argc();
	cmd = Cmd_Argv(0);
	val = Cmd_Argv(1);

	if (!argc)
	{
	}
	//create a new body
	else if (argc == 3 && !stricmp(cmd, "body"))
	{
		int boneidx = Mod_TagNumForName(d->model, Cmd_Argv(2))-1;
		ctx->joint = NULL;
		ctx->body = NULL;
		if (boneidx >= 0)
		{
			d->body = BZ_Realloc(d->body, sizeof(*d->body)*(d->numbodies+1));
			ctx->body = &d->body[d->numbodies];
			d->bone[boneidx].bodyidx = d->numbodies;
			d->numbodies++;

			*ctx->body = ctx->defbody;
			Q_strncpyz(ctx->body->name, Cmd_Argv(1), sizeof(ctx->body->name));
			ctx->body->bone = boneidx;
		}
		else if (!ctx->errors++)
			Con_Printf("^[Unable to create body \"%s\" because bone \"%s\" does not exist in %s\\edit\\%s %i^]\n", Cmd_Argv(1), Cmd_Argv(2), d->model->name, d->name, linenum);
	}
	//create a new joint
	else if (argc >= 2 && !stricmp(cmd, "joint"))
	{
		char *name;
		ctx->joint = NULL;
		ctx->body = NULL;
		d->joint = BZ_Realloc(d->joint, sizeof(*d->joint)*(d->numjoints+1));
		ctx->joint = &d->joint[d->numjoints];
		*ctx->joint = ctx->defjoint;
		Q_strncpyz(ctx->joint->name, Cmd_Argv(1), sizeof(ctx->joint->name));
		name = Cmd_Argv(2);
		ctx->joint->body1 = *name?rag_finddollbody(d, name):-1;
		if (*name && ctx->joint->body1 < 0)
		{
			if (!ctx->errors++)
				Con_Printf("^[Joint \"%s\" joins invalid body \"%s\"\\edit\\%s %i^]\n", ctx->joint->name, name, d->name, linenum);
			return true;
		}
		name = Cmd_Argv(3);
		ctx->joint->body2 = *name?rag_finddollbody(d, name):-1;
		if (*name && (ctx->joint->body2 < 0 || ctx->joint->body2 == ctx->joint->body1))
		{
			if (!ctx->errors++)
			{
				if (ctx->joint->body2 == ctx->joint->body1)
					Con_Printf("^[Joint \"%s\" joints body \"%s\" to itself\\edit\\%s %i^]\n", ctx->joint->name, name, d->name, linenum);
				else
					Con_Printf("^[Joint \"%s\" joints invalid body \"%s\"\\edit\\%s %i^]\n", ctx->joint->name, name, d->name, linenum);
			}
			return true;
		}
		ctx->joint->bonepivot = d->body[(ctx->joint->body2 >= 0)?ctx->joint->body2:ctx->joint->body1].bone;	//default the pivot object to the bone of the second object.

		if (ctx->joint->body1 >= 0 || ctx->joint->body2 >= 0)
			d->numjoints++;
		else if (!ctx->errors++)
			Con_Printf("^[Joint property \"%s\" not recognised\\edit\\%s %i^]\n", ctx->joint->name, d->name, linenum);
	}
	else if (argc == 2 && !stricmp(cmd, "updatebody"))
	{
		ctx->joint = NULL;
		ctx->body = NULL;
		if (!strcmp(val, "default"))
			ctx->body = &ctx->defbody;
		else
		{
			i = rag_finddollbody(d, val);
			if (i >= 0)
				ctx->body = &d->body[i];
			else if (!ctx->errors++)
				Con_Printf("^[Cannot update body \"%s\"\\edit\\%s %i^]\n", ctx->body->name, d->name, linenum);
		}
	}
	else if (argc == 2 && !stricmp(cmd, "updatejoint"))
	{
		ctx->joint = NULL;
		ctx->body = NULL;
		if (!strcmp(val, "default"))
			ctx->joint = &ctx->defjoint;
		else
		{
			i = rag_finddolljoint(d, val);
			if (i >= 0)
				ctx->joint = &d->joint[i];
			else if (!ctx->errors++)
				Con_Printf("^[Cannot update joint \"%s\"\\edit\\%s %i^]\n", ctx->joint->name, d->name, linenum);
		}
	}

	//body properties
	else if (ctx->body && argc == 2 && !stricmp(cmd, "shape"))
	{
		if (!stricmp(val, "box"))
			ctx->body->shape = SOLID_PHYSICS_BOX;
		else if (!stricmp(val, "sphere"))
			ctx->body->shape = SOLID_PHYSICS_SPHERE;
		else if (!stricmp(val, "cylinder"))
			ctx->body->shape = SOLID_PHYSICS_CYLINDER;
		else if (!stricmp(val, "capsule"))
			ctx->body->shape = SOLID_PHYSICS_CAPSULE;
		else if (!ctx->errors++)
			Con_Printf("^[Joint shape \"%s\" not recognised\\edit\\%s %i^]\n", val, d->name, linenum);
	}
	else if (ctx->body && argc == 2 && !stricmp(cmd, "animate"))
		ctx->body->animate = atof(val);
	else if (ctx->body && argc == 2 && !stricmp(cmd, "draw"))
		ctx->body->draw = atoi(val);
	else if (ctx->body && argc == 2 && !stricmp(cmd, "mass"))
		ctx->body->mass = atof(val);
	else if (ctx->body && argc == 2 && (!stricmp(cmd, "dimensions") || !stricmp(cmd, "size")))
		ctx->body->dimensions[0] = ctx->body->dimensions[1] = ctx->body->dimensions[2] = atof(val);
	else if (ctx->body && argc == 3 && (!stricmp(cmd, "dimensions") || !stricmp(cmd, "size")))
	{
		ctx->body->dimensions[0] = ctx->body->dimensions[1] = atof(val);
		ctx->body->dimensions[2] = atoi(Cmd_Argv(2));
	}
	else if (ctx->body && argc == 4 && (!stricmp(cmd, "dimensions") || !stricmp(cmd, "size")))
	{
		ctx->body->dimensions[0] = atof(val);
		ctx->body->dimensions[1] = atof(Cmd_Argv(2));
		ctx->body->dimensions[2] = atof(Cmd_Argv(3));
	}

	//joint properties
	else if (ctx->joint && argc == 2 && !stricmp(cmd, "type"))
	{
		if (!stricmp(val, "fixed"))
			ctx->joint->type = JOINTTYPE_FIXED;
		else if (!stricmp(val, "point"))
			ctx->joint->type = JOINTTYPE_POINT;
		else if (!stricmp(val, "hinge"))
			ctx->joint->type = JOINTTYPE_HINGE;
		else if (!stricmp(val, "slider"))
			ctx->joint->type = JOINTTYPE_SLIDER;
		else if (!stricmp(val, "universal"))
			ctx->joint->type = JOINTTYPE_UNIVERSAL;
		else if (!stricmp(val, "hinge2"))
			ctx->joint->type = JOINTTYPE_HINGE2;
	}
	else if (ctx->joint && argc == 2 && !stricmp(cmd, "draw"))
		ctx->joint->draw = atoi(val);
	else if (ctx->joint && argc == 2 && !stricmp(cmd, "ERP"))
		ctx->joint->ERP = atof(val);
	else if (ctx->joint && argc == 2 && !stricmp(cmd, "ERP2"))
		ctx->joint->ERP2 = atof(val);
	else if (ctx->joint && argc == 2 && !stricmp(cmd, "CFM"))
		ctx->joint->CFM = atof(val);
	else if (ctx->joint && argc == 2 && !stricmp(cmd, "CFM2"))
		ctx->joint->CFM2 = atof(val);
	else if (ctx->joint && argc == 2 && !stricmp(cmd, "FMax"))
		ctx->joint->FMax = atof(val);
	else if (ctx->joint && argc == 2 && !stricmp(cmd, "FMax2"))
		ctx->joint->FMax2 = atof(val);
	else if (ctx->joint && argc == 2 && !stricmp(cmd, "HiStop"))
		ctx->joint->HiStop = atof(val);
	else if (ctx->joint && argc == 2 && !stricmp(cmd, "HiStop2"))
		ctx->joint->HiStop2 = atof(val);
	else if (ctx->joint && argc == 2 && !stricmp(cmd, "LoStop"))
		ctx->joint->LoStop = atof(val);
	else if (ctx->joint && argc == 2 && !stricmp(cmd, "LoStop2"))
		ctx->joint->LoStop2 = atof(val);
	else if (ctx->joint && argc == 4 && !stricmp(cmd, "axis"))
	{
		ctx->joint->axis[0] = atof(val);
		ctx->joint->axis[1] = atof(Cmd_Argv(2));
		ctx->joint->axis[2] = atof(Cmd_Argv(3));
	}
	else if (ctx->joint && argc == 4 && !stricmp(cmd, "axis2"))
	{
		ctx->joint->axis2[0] = atof(val);
		ctx->joint->axis2[1] = atof(Cmd_Argv(2));
		ctx->joint->axis2[2] = atof(Cmd_Argv(3));
	}
	else if (ctx->joint && argc == 4 && !stricmp(cmd, "offset"))
	{
		ctx->joint->offset[0] = atof(val);
		ctx->joint->offset[1] = atof(Cmd_Argv(2));
		ctx->joint->offset[2] = atof(Cmd_Argv(3));
	}
	else if (ctx->joint && ctx->joint != &ctx->defjoint && (argc == 2 || argc == 5) && !stricmp(cmd, "pivot"))
	{
		//the origin is specified in base-frame model space
		//we need to make it relative to the joint's bodies
		char *bone = val;
		i = Mod_TagNumForName(d->model, bone)-1;
		if (argc > 2)
		{
			ctx->joint->offset[0] = atof(Cmd_Argv(2));
			ctx->joint->offset[1] = atof(Cmd_Argv(3));
			ctx->joint->offset[2] = atof(Cmd_Argv(4));
		}
		if (i >= 0)
		{
			ctx->joint->bonepivot = i;
			//Matrix3x4_Multiply(omat, bones[i].inverse, joint->orgmatrix);
		}
		else if (!ctx->errors++)
			Con_Printf("^[Directive \"%s\" not understood or invalid\\edit\\%s %i^]\n", cmd, d->name, linenum);
	}
	else
		return false;

	return true;
};
doll_t *rag_finishdoll(dollcreatectx_t *ctx)
{
	doll_t *d = ctx->d;
	int i;

	d->drawn = false;
	for (i = 0; i < d->numbodies; i++)
	{
		if (d->body[i].draw)
		{
			d->drawn = true;
			break;
		}
	}
	if (i == d->numbodies)
	for (i = 0; i < d->numjoints; i++)
	{
		if (d->joint[i].draw)
		{
			d->drawn = true;
			break;
		}
	}

	free(ctx);

	if (!d->numbodies)
	{
		rag_freedoll(d);
		return NULL;
	}
	return d;
};

doll_t *rag_createdollfromstring(model_t *mod, char *fname, int numbones, char *file)
{
	int linenum = 0;
	dollcreatectx_t *ctx;
	ctx = rag_createdoll(mod, fname, numbones);
	if (!ctx)
		return NULL;
	
	while(file && *file)
	{
		linenum++;
		file = Cmd_TokenizeString(file, false, false);

		if (!rag_dollline(ctx, linenum))
			if (!ctx->errors++)
				Con_Printf("^[Directive \"%s\" not understood or invalid\\edit\\%s %i^]\n", Cmd_Argv(0), ctx->d->name, linenum);
	}
	return rag_finishdoll(ctx);
}

static doll_t *rag_loaddoll(model_t *mod, char *fname, int numbones)
{
	doll_t *d;
	void *fptr = NULL;
	int fsize;

	for (d = dolllist; d; d = d->next)
	{
		if (d->model == mod)
			if (!strcmp(d->name, fname))
				return d;
	}

	fsize = FS_LoadFile(fname, &fptr);
	if (!fptr)
	{
#ifndef SERVERONLY
		CL_CheckOrEnqueDownloadFile(fname, NULL, 0);
#endif
		return NULL;
	}

	d = rag_createdollfromstring(mod, fname, numbones, fptr);
	FS_FreeFile(fptr);

	if (d)
	{
		d->next = dolllist;
		dolllist = d;
	}
	return d;
}
void rag_freedoll(doll_t *doll)
{
	int i;
	if (doll->uses)
	{
		for (i = 0; i < numskelobjectsused; i++)
		{
			if (skelobjects[i].doll == doll)
			{
				rag_uninstanciate(&skelobjects[i]);
				if (!doll->uses)
					break;
			}
		}
	}
	BZ_Free(doll->bone);
	BZ_Free(doll->body);
	BZ_Free(doll->joint);
	free(doll->name);
	BZ_Free(doll);
}

void rag_flushdolls(qboolean force)
{
	doll_t *d, **link;
	int i;
	if (force)
	{
		for (i = 0; i < numskelobjectsused; i++)
		{
			rag_uninstanciate(&skelobjects[i]);
		}
	}
	for (link = &dolllist; *link; )
	{
		d = *link;
		if (!d->uses)
		{
			*link = d->next;
			rag_freedoll(d);
		}
		else
			link = &(*link)->next;
	}
}

void skel_integrate(pubprogfuncs_t *prinst, skelobject_t *sko, skelobject_t *skelobjsrc, float ft, float mmat[12])
{
#if 0
	trace_t t;
	vec3_t npos, opos, wnpos, wopos;
	vec3_t move;
	float wantmat[12];
	world_t *w = prinst->parms->user;
	body_t *b;
	float gravity = 800;
	int bone, bno;
	int boffs;
	galiasbone_t *boneinfo = Mod_GetBoneInfo(sko->model);
	if (!boneinfo)
		return;

	for (bone = 0, bno = 0, b = sko->body; bone < sko->numbones; bone++)
	{
		boffs = bone*12;
		/*if this bone is positioned using a physical body...*/
		if (bno < sko->numbodies && b->jointo == boffs)
		{
			if (skelobjsrc)
			{
				/*attempt to move to target*/
				if (boneinfo[bone].parent >= 0)
				{
					Matrix3x4_Multiply(skelobjsrc->bonematrix+boffs, sko->bonematrix+12*boneinfo[bone].parent, wantmat);
				}
				else
				{
					Vector4Copy(skelobjsrc->bonematrix+boffs+0, wantmat+0);
					Vector4Copy(skelobjsrc->bonematrix+boffs+4, wantmat+4);
					Vector4Copy(skelobjsrc->bonematrix+boffs+8, wantmat+8);
				}
				b->vel[0] = (wantmat[3 ] - sko->bonematrix[b->jointo + 3 ])/ft;
				b->vel[1] = (wantmat[7 ] - sko->bonematrix[b->jointo + 7 ])/ft;
				b->vel[2] = (wantmat[11] - sko->bonematrix[b->jointo + 11])/ft;
			}
			else
			{
				/*handle gravity*/
				b->vel[2] = b->vel[2] - gravity * ft / 2;
			}

			VectorScale(b->vel, ft, move);

			opos[0] = sko->bonematrix[b->jointo + 3 ];
			opos[1] = sko->bonematrix[b->jointo + 7 ];
			opos[2] = sko->bonematrix[b->jointo + 11];
			npos[0] = opos[0] + move[0];
			npos[1] = opos[1] + move[1];
			npos[2] = opos[2] + move[2];

			Matrix3x4_RM_Transform3(mmat, opos, wopos);
			Matrix3x4_RM_Transform3(mmat, npos, wnpos);

			t = World_Move(w, wopos, vec3_origin, vec3_origin, wnpos, MOVE_NOMONSTERS, w->edicts);
			if (t.startsolid)
				t.fraction = 1;
			else if (t.fraction < 1)
			{
				/*clip the velocity*/
				float backoff = -DotProduct (b->vel, t.plane.normal) * 1.9; /*teehee, bouncy corpses*/
				VectorMA(b->vel, backoff, t.plane.normal, b->vel);
			}
			if (skelobjsrc)
			{
				VectorCopy(wantmat+0, sko->bonematrix+b->jointo+0);
				VectorCopy(wantmat+4, sko->bonematrix+b->jointo+4);
				VectorCopy(wantmat+8, sko->bonematrix+b->jointo+8);
			}

			sko->bonematrix[b->jointo + 3 ] += move[0] * t.fraction;
			sko->bonematrix[b->jointo + 7 ] += move[1] * t.fraction;
			sko->bonematrix[b->jointo + 11] += move[2] * t.fraction;

			if (!skelobjsrc)
				b->vel[2] = b->vel[2] - gravity * ft / 2;

			b++;
			bno++;
		}
		else if (skelobjsrc)
		{
			/*directly copy animation skeleton*/
			if (boneinfo[bone].parent >= 0)
			{
				Matrix3x4_Multiply(skelobjsrc->bonematrix+boffs, sko->bonematrix+12*boneinfo[bone].parent, sko->bonematrix+boffs);
			}
			else
			{
				Vector4Copy(skelobjsrc->bonematrix+boffs+0, sko->bonematrix+boffs+0);
				Vector4Copy(skelobjsrc->bonematrix+boffs+4, sko->bonematrix+boffs+4);
				Vector4Copy(skelobjsrc->bonematrix+boffs+8, sko->bonematrix+boffs+8);
			}
		}
		else
		{
			/*retain the old relation*/
			/*FIXME*/
		}
	}
#endif
	/*debugging*/
#if 0
	/*draw points*/
	for (bone = 0; p < sko->numbones; bone++)
	{
		opos[0] = sko->bonematrix[bone*12 + 3 ];
		opos[1] = sko->bonematrix[bone*12 + 7 ];
		opos[2] = sko->bonematrix[bone*12 + 11];
		Matrix3x4_RM_Transform3(mmat, opos, wopos);
		P_RunParticleEffectTypeString(wopos, vec3_origin, 1, "ragdolltest");
	}
#endif
}
#endif

/*destroys all skeletons*/
void skel_reset(pubprogfuncs_t *prinst)
{
	int i;

	for (i = 0; i < numskelobjectsused; i++)
	{
		if (skelobjects[i].world == prinst->parms->user)
		{
			rag_uninstanciate(&skelobjects[i]);
			skelobjects[i].numbones = 0;
			skelobjects[i].inuse = false;
			skelobjects[i].bonematrix = NULL;
		}
	}

	while (numskelobjectsused && !skelobjects[numskelobjectsused-1].inuse)
		numskelobjectsused--;
	rag_flushdolls(false);
}

/*deletes any skeletons marked for deletion*/
void skel_dodelete(pubprogfuncs_t *prinst)
{
	int skelidx;
	if (!pendingkill)
		return;

	pendingkill = false;
	for (skelidx = 0; skelidx < numskelobjectsused; skelidx++)
	{
		if (skelobjects[skelidx].inuse == 2)
		{
			rag_uninstanciate(&skelobjects[skelidx]);
			skelobjects[skelidx].inuse = 0;
		}
	}

	while (numskelobjectsused && !skelobjects[numskelobjectsused-1].inuse)
		numskelobjectsused--;
}

skelobject_t *skel_create(pubprogfuncs_t *prinst, int bonecount)
{
	unsigned int skelidx;
	//invalid if the bonecount is not set...
	if (bonecount <= 0 || bonecount > MAX_BONES)
		return NULL;

	for (skelidx = 0; skelidx < numskelobjectsused; skelidx++)
	{
		if (!skelobjects[skelidx].inuse && skelobjects[skelidx].numbones == bonecount && skelobjects[skelidx].world == prinst->parms->user)
		{
			skelobjects[skelidx].inuse = 1;
			return &skelobjects[skelidx];
		}
	}

	for (skelidx = 0; skelidx <= MAX_SKEL_OBJECTS; skelidx++)
	{
		if (!skelobjects[skelidx].inuse &&
			(!skelobjects[skelidx].numbones || skelobjects[skelidx].numbones == bonecount) &&
			(!skelobjects[skelidx].world || skelobjects[skelidx].world == prinst->parms->user))
		{
			if (!skelobjects[skelidx].numbones)
			{
				skelobjects[skelidx].numbones = bonecount;
				skelobjects[skelidx].bonematrix = (float*)PR_AddString(prinst, "", sizeof(float)*12*bonecount);
			}
			skelobjects[skelidx].world = prinst->parms->user;
			if (numskelobjectsused <= skelidx)
				numskelobjectsused = skelidx + 1;
			skelobjects[skelidx].model = NULL;
			skelobjects[skelidx].inuse = 1;
			return &skelobjects[skelidx];
		}
	}

	return NULL;
}
skelobject_t *skel_get(pubprogfuncs_t *prinst, int skelidx)
{
	skelidx--;
	if ((unsigned int)skelidx >= numskelobjectsused)
		return NULL;
	if (skelobjects[skelidx].inuse != 1)
		return NULL;
	return &skelobjects[skelidx];
}

void skel_lookup(pubprogfuncs_t *prinst, int skelidx, framestate_t *out)
{
	skelobject_t *sko = skel_get(prinst, skelidx);
	if (sko && sko->inuse)
	{
		out->boneabs = sko->type;
		out->bonecount = sko->numbones;
		out->bonestate = sko->bonematrix;
	}
}

void QCBUILTIN PF_skel_mmap(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	int skelidx = G_FLOAT(OFS_PARM0);
	skelobject_t *sko = skel_get(prinst, skelidx);
	if (!sko || sko->world != prinst->parms->user)
		G_INT(OFS_RETURN) = 0;
	else
		G_INT(OFS_RETURN) = (char*)sko->bonematrix - prinst->stringtable;
}

//may not poke the skeletal object bone data.
void rag_uninstanciate(skelobject_t *sko)
{
	int i;
	if (!sko->doll)
		return;

	for (i = 0; i < sko->numbodies; i++)
	{
		World_ODE_RagDestroyBody(sko->world, &sko->body[i].odebody);
	}
	BZ_Free(sko->body);
	sko->body = NULL;
	sko->numbodies = 0;

	for (i = 0; i < sko->numjoints; i++)
	{
		World_ODE_RagDestroyJoint(sko->world, &sko->joint[i]);
	}
	BZ_Free(sko->joint);
	sko->joint = NULL;
	sko->numjoints = 0;

	sko->doll->uses--;
	sko->doll = NULL;
}
void rag_genbodymatrix(skelobject_t *sko, odebodyinfo_t *dollbody, float *emat, float *result)
{
	float *bmat;
	int bone = dollbody->bone;
	bmat = sko->bonematrix + bone*12;
	R_ConcatTransforms((void*)emat, (void*)bmat, (void*)result);

	if (dollbody->orient)
	{
		float peer[12];
		bone = dollbody->orientpeer;
		bmat = sko->bonematrix + bone*12;
		
		R_ConcatTransforms((void*)emat, (void*)bmat, (void*)peer);
	}

	//FIXME: handle biasing it to point away from the parent bone towards an orientation bone
}
qboolean rag_animate(skelobject_t *sko, doll_t *doll, float *emat)
{
	//drive the various animated bodies to their updated positions
	int i;
	for (i = 0; i < sko->numbodies; i++)
	{
		if (!doll->body[i].animate)
			continue;
		rag_genbodymatrix(sko, &doll->body[i], emat, sko->body[i].animmatrix);
	}
	return true;
}
qboolean rag_instanciate(skelobject_t *sko, doll_t *doll, float *emat, wedict_t *ent)
{
	int i;
	float *bmat;
	float bodymat[12], worldmat[12];
	vec3_t aaa2[3];
	galiasbone_t *bones = Mod_GetBoneInfo(sko->model);
	int bone;
	odebody_t *body1, *body2;
	odejointinfo_t *j;
	sko->numbodies = doll->numbodies;
	sko->body = BZ_Malloc(sizeof(*sko->body) * sko->numbodies);
	sko->doll = doll;
	doll->uses++;
	sko->numanimated = 0;
	for (i = 0; i < sko->numbodies; i++)
	{
		memset(&sko->body[i], 0, sizeof(sko->body[i]));

		sko->body[i].animstrength = doll->body[i].animate;
		if (sko->body[i].animstrength)
			sko->numanimated++;

		//spawn the body in the base pose, so we can add joints etc (also ignoring the entity matrix, we'll fix all that up later).
		if (1)
			Matrix3x4_Invert_Simple(bones[doll->body[i].bone].inverse, bodymat);
		else
			rag_genbodymatrix(sko, &doll->body[i], emat, bodymat);
		if (!World_ODE_RagCreateBody(sko->world, &sko->body[i].odebody, &doll->body[i], bodymat, ent))
			return false;
	}
	sko->numjoints = doll->numjoints;
	sko->joint = BZ_Malloc(sizeof(*sko->joint) * sko->numjoints);
	memset(sko->joint, 0, sizeof(*sko->joint) * sko->numjoints);
	for(i = 0; i < sko->numjoints; i++)
	{
		j = &doll->joint[i];
		body1 = j->body1>=0?&sko->body[j->body1].odebody:NULL;
		body2 = j->body2>=0?&sko->body[j->body2].odebody:NULL;

		bone = j->bonepivot;
		bmat = sko->bonematrix + bone*12;

		if (1)
		{
			Matrix3x4_Invert_Simple(bones[j->bonepivot].inverse, worldmat);
		}
		else
		{
			memcpy(bodymat, bmat, sizeof(bodymat));
			bodymat[3] += j->offset[0];
			bodymat[3+4] += j->offset[1];
			bodymat[3+8] += j->offset[2];
			R_ConcatTransforms((void*)emat, (void*)bodymat, (void*)worldmat);
		}
		aaa2[0][0] = worldmat[3];
		aaa2[0][1] = worldmat[3+4];
		aaa2[0][2] = worldmat[3+8];
		VectorNormalize2(j->axis, aaa2[1]);
		VectorNormalize2(j->axis2, aaa2[2]);

		World_ODE_RagCreateJoint(sko->world, &sko->joint[i], j, body1, body2, aaa2);
	}

	//now the joints have all their various properties, move the bones to their real positions.
	//this might result in the body flying across the room...
	for (i = 0; i < sko->numbodies; i++)
	{
		rag_genbodymatrix(sko, &doll->body[i], emat, bodymat);
		World_ODE_RagMatrixToBody(&sko->body[i].odebody, bodymat);
	}

	sko->doll->numdefaultanimated = sko->numanimated;
	return true;
}

void CLQ1_AddOrientedCube(shader_t *shader, vec3_t mins, vec3_t maxs, float *matrix, float r, float g, float b, float a);
void CLQ1_AddOrientedCylinder(shader_t *shader, float radius, float height, qboolean capsule, float *matrix, float r, float g, float b, float a);
void CLQ1_DrawLine(shader_t *shader, vec3_t v1, vec3_t v2, float r, float g, float b, float a);
void rag_derive(skelobject_t *sko, skelobject_t *asko, float *emat)
{
	doll_t *doll = sko->doll;
	float *bmat = sko->bonematrix;
	float *amat = asko?asko->bonematrix:NULL;
	galiasbone_t *bones = Mod_GetBoneInfo(sko->model);
	int i;
	float invemat[12];
	float bodymat[12], rel[12];

	Matrix3x4_Invert(emat, invemat);

#ifndef SERVERONLY
	if (doll->drawn)
	{
		float rad;
		vec3_t mins, maxs;
		shader_t *debugshader = NULL;
		shader_t *lineshader = NULL;
		vec3_t start, end;
		for (i = 0; i < sko->numbodies; i++)
		{
			if (!doll->body[i].draw)
				continue;

			if (!debugshader)
				debugshader = R_RegisterShader("boneshader",
					"{\n"
					"polygonoffset\n"
					"{\n"
					"map $whiteimage\n"
					"blendfunc add\n"
					"rgbgen vertex\n"
					"alphagen vertex\n"
					"}\n"
					"}\n");
			
			World_ODE_RagMatrixFromBody(sko->world, &sko->body[i].odebody, bodymat);

			switch(doll->body[i].shape)
			{
			default:
			case SOLID_PHYSICS_BOX:
				VectorScale(doll->body[i].dimensions, -0.5, mins);
				VectorScale(doll->body[i].dimensions, 0.5, maxs);
				CLQ1_AddOrientedCube(debugshader, mins, maxs, bodymat, 0.2, 0.2, 0.2, 1);
				break;
			case SOLID_PHYSICS_CYLINDER:
				rad = (doll->body[i].dimensions[0] + doll->body[i].dimensions[1])*0.5;
				CLQ1_AddOrientedCylinder(debugshader, rad, doll->body[i].dimensions[2], false, bodymat, 0.2, 0.2, 0.2, 1);
				break;
			case SOLID_PHYSICS_CAPSULE:
				rad = (doll->body[i].dimensions[0] + doll->body[i].dimensions[1])*0.5;
				CLQ1_AddOrientedCylinder(debugshader, rad, doll->body[i].dimensions[2], true, bodymat, 0.2, 0.2, 0.2, 1);
				break;
			case SOLID_PHYSICS_SPHERE:
				rad = (doll->body[i].dimensions[0] + doll->body[i].dimensions[1] + doll->body[i].dimensions[2])/3;
				CLQ1_AddOrientedCylinder(debugshader, rad, rad, true, bodymat, 0.2, 0.2, 0.2, 1);
				break;
			}
		}
		mins[0] = mins[1] = mins[2] = -1;
		maxs[0] = maxs[1] = maxs[2] = 1;
		for (i = 0; i < doll->numjoints; i++)
		{
			if (!doll->joint[i].draw)
				continue;
			World_ODE_RagMatrixFromJoint(&sko->joint[i], &doll->joint[i], bodymat);
	//		CLQ1_AddOrientedCube(debugshader, mins, maxs, bodymat, 0, 0.2, 0, 1);

			if (!lineshader)
				lineshader = R_RegisterShader("lineshader",
					"{\n"
					"polygonoffset\n"
					"{\n"
					"map $whiteimage\n"
					"blendfunc add\n"
					"rgbgen vertex\n"
					"alphagen vertex\n"
					"}\n"
					"}\n");
			start[0] = bodymat[3];
			start[1] = bodymat[7];
			start[2] = bodymat[11];
			end[0] = bodymat[3] + bodymat[8]*4;
			end[1] = bodymat[7] + bodymat[9]*4;
			end[2] = bodymat[11] + bodymat[10]*4;
			CLQ1_DrawLine(lineshader, start, end, 0, 1, 1, 1);
			start[0] = bodymat[3] + bodymat[4]*-2;
			start[1] = bodymat[7] + bodymat[5]*-2;
			start[2] = bodymat[11] + bodymat[6]*-2;
			end[0] = bodymat[3] + bodymat[4]*2;
			end[1] = bodymat[7] + bodymat[5]*2;
			end[2] = bodymat[11] + bodymat[6]*2;
			CLQ1_DrawLine(lineshader, start, end, 1, 1, 0, 1);
		}
	}
#endif
	for (i = 0; i < doll->numbones; i++)
	{
		if (doll->bone[i].bodyidx >= 0)
		{
			//bones with a body are given an absolute pose matching that body.
			World_ODE_RagMatrixFromBody(sko->world, &sko->body[doll->bone[i].bodyidx].odebody, bodymat);
			//that body matrix is in world space, so transform to model space for our result
			R_ConcatTransforms((void*)invemat, (void*)bodymat, (void*)((float*)bmat+i*12));
		}
		else if (amat)	//FIXME: don't do this when the bone has an unanimated child body.
		{
			//this bone has no joint object, use the anim sko's relative pose info instead
			if (bones[i].parent >= 0)
				R_ConcatTransforms((void*)(bmat + bones[i].parent*12), (void*)((float*)amat+i*12), (void*)((float*)bmat+i*12));
			else
				memcpy((void*)((float*)bmat+i*12), (void*)((float*)amat+i*12), sizeof(float)*12);
		}
		else
		{
			//copy from the base pose
			Matrix3x4_Invert_Simple(bones[i].inverse, bodymat);
			//that's the absolute pose...
			if (bones[i].parent >= 0)
			{
				R_ConcatTransforms((void*)(bones[bones[i].parent].inverse), (void*)bodymat, (void*)rel);
				R_ConcatTransforms((void*)(bmat + bones[i].parent*12), (void*)rel, (void*)((float*)bmat+i*12));
			}
			else
			{
				//its all absolute when its the root.
				memcpy((void*)((float*)bmat+i*12), bodymat, sizeof(float)*12);
			}
		}
	}

	//if it wasn't before, it definitely is now.
	sko->type = SKOT_ABSOLUTE;
}

//called each physics frame to update the body velocities for animation
void rag_doallanimations(world_t *world)
{
	int i, j;
	doll_t *doll;
	skelobject_t *sko;
	for (i = 0; i < numskelobjectsused; i++)
	{
		sko = &skelobjects[i];
		if (sko->world != world)
			continue;
		doll = sko->doll;
		if (!doll || !sko->numanimated)
			continue;

		for (j = 0; j < sko->numbodies; j++)
		{
			if (!doll->body[j].animate)
				continue;
			World_ODE_RagMatrixToBody(&sko->body[j].odebody, sko->body[j].animmatrix);
		}
	}
}

#ifndef SERVERONLY
void rag_removedeltaent(lerpents_t *le)
{
	extern world_t csqc_world;
	int skelidx = le->skeletalobject;
	skelobject_t *skelobj;

	if (!skelidx)
		return;
	le->skeletalobject = 0;

	skelobj = skel_get(csqc_world.progs, skelidx);
	if (skelobj)
	{
		skelobj->inuse = 2;	//2 means don't reuse yet.
		skelobj->model = NULL;
		pendingkill = true;
	}
}

void rag_updatedeltaent(entity_t *ent, lerpents_t *le)
{
	extern world_t csqc_world;
	world_t *w;
	model_t *mod = ent->model;
	skelobject_t *sko;
	float emat[12];
	skelobject_t skorel = {0};
	float relmat[MAX_BONES*12];
	skorel.bonematrix = relmat;
	skorel.type = SKOT_RELATIVE;

	if (mod->dollinfo)
	{
		w = &csqc_world;
		if (!w->ode.ode)
			return;

		if (!le->skeletalobject)
		{
			sko = skel_create(w->progs, Mod_GetNumBones(mod, false));
			if (!sko)
				return;	//couldn't get one, ran out of memory or something?
			sko->model = mod;
			sko->type = SKOT_RELATIVE;
			le->skeletalobject = (sko - skelobjects) + 1;
		}
		else
		{
			sko = skel_get(w->progs, le->skeletalobject);
			if (!sko)
			{
				le->skeletalobject = 0;
				return;	//couldn't get one, ran out of memory or something?
			}
		}

		skorel.numbones = sko->numbones;

		//FIXME: provide some way for the animation to auto-trigger ragdoll (so framegroups can work automagically)
		if (ent->framestate.g[FS_REG].frame[0] == 65535 || ent->framestate.g[FS_REG].frame[1] == 65535)
			sko->numanimated = 0;
		else if (sko->doll)
			sko->numanimated = sko->doll->numdefaultanimated;
		Mod_GetBoneRelations(mod, 0, skorel.numbones, &ent->framestate, skorel.bonematrix);
		skorel.model = sko->model;
		if (sko->numanimated || sko->doll != mod->dollinfo)
		{
			skel_copy_toabs(sko, &skorel, 0, sko->numbones);
		}

		bonemat_fromaxisorg(emat, ent->axis, ent->origin);

		if (sko->doll != mod->dollinfo)
		{
			rag_uninstanciate(sko);
			rag_instanciate(sko, mod->dollinfo, emat, NULL);
		}
		if (sko->numanimated)
			rag_animate(sko, sko->doll, emat);

		rag_derive(sko, sko->numanimated?&skorel:NULL, emat);

		ent->framestate.bonestate = sko->bonematrix;
		ent->framestate.bonecount = sko->numbones;
		ent->framestate.boneabs = sko->type == SKOT_ABSOLUTE;
	}
}
#endif
#endif

#ifdef SKELETALOBJECTS
//update a skeletal object to track its ragdoll/apply a ragdoll to a skeletal object.
void QCBUILTIN PF_skel_ragedit(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	//do we want to be able to generate a ragdoll object with this function too?
#ifdef RAGDOLL
	wedict_t *wed = (wedict_t*)G_EDICT(prinst, OFS_PARM0);
	char *ragname = PR_GetStringOfs(prinst, OFS_PARM1);
	int parentskel = G_FLOAT(OFS_PARM2);
	int skelidx;
	skelobject_t *sko, *psko;
	doll_t *doll;
	float emat[12];

	vec3_t d[3], a;
	//fixme: respond to renderflags&USEAXIS? scale?
	a[0] = wed->v->angles[0] * -1; /*mod_alias bug*/
	a[1] = wed->v->angles[1];
	a[2] = wed->v->angles[2];
	AngleVectors(a, d[0], d[1], d[2]);
	bonemat_fromqcvectors(emat, d[0], d[1], d[2], wed->v->origin);
	skelidx = wed->xv->skeletonindex;

	G_FLOAT(OFS_RETURN) = 0;

	//the parent skeletal object must be relative, if specified.
	psko = skel_get(prinst, parentskel);
	if (psko && psko->type != SKOT_RELATIVE)
		return;

	sko = skel_get(prinst, skelidx);
	if (!sko)
	{
		Con_DPrintf("PF_skel_ragedit: invalid skeletal object\n");
		return;
	}

	if (!sko->world->ode.ode)
	{
		Con_DPrintf("PF_skel_ragedit: ODE not enabled\n");
		return;
	}

	if (sko->doll)	//use the current doll
		doll = sko->doll;
	else if (sko->model) //
		doll = sko->model->dollinfo;
	else
		doll = NULL;

	if (*ragname)
	{
		int idx;
		char *cmd;

		ragname = Cmd_TokenizeString(ragname, false, false);
		cmd = Cmd_Argv(0);
		if (!stricmp(cmd, "enablejoint"))
		{
			idx = rag_finddolljoint(sko->doll, Cmd_Argv(1));
			World_ODE_RagEnableJoint(&sko->joint[idx], atoi(Cmd_Argv(2)));
			G_FLOAT(OFS_RETURN) = 1;
			return;
		}
		else if (!stricmp(cmd, "animatebody"))
		{
			int body = rag_finddollbody(sko->doll, Cmd_Argv(1));
			float strength = atof(Cmd_Argv(2));
			if (body >= 0)
			{
				if (sko->body[body].animstrength)
					sko->numanimated--;
				sko->body[body].animstrength = strength;
				if (sko->body[body].animstrength)
					sko->numanimated++;
			}
			G_FLOAT(OFS_RETURN) = sko->numanimated;
			return;
		}
		else if (!stricmp(cmd, "animate"))
		{
			float strength = atof(Cmd_Argv(1));
			int i;
			sko->numanimated = 0;

			for (i = 0; i < sko->numbodies; i++)
			{
				sko->body[i].animstrength = sko->doll->body[i].animate * strength;
				if (sko->body[i].animstrength)
					sko->numanimated++;
			}

			if (sko->numanimated)
			{
				//make sure the animation target is valid.
				skel_copy_toabs(sko, psko?psko:sko, 0, sko->numbones);
				rag_animate(sko, sko->doll, emat);
			}
			G_FLOAT(OFS_RETURN) = 1;
			return;
		}
		else if (!stricmp(cmd, "doll"))
			doll = rag_loaddoll(sko->model, Cmd_Argv(1), sko->numbones);
		else if (!stricmp(cmd, "dollstring"))
			doll = rag_createdollfromstring(sko->model, "", sko->numbones, ragname);
		else if (!stricmp(cmd, "cleardoll"))
			doll = NULL;
		else
		{
			Con_Printf("PF_skel_ragedit: Unsupported command.\n");
			return;
		}
	}

	if (sko->doll != doll)
	{
		rag_uninstanciate(sko);
		if (!doll)
		{
			G_FLOAT(OFS_RETURN) = 1;	//technically success.
			return;
		}
		skel_copy_toabs(sko, psko?psko:sko, 0, sko->numbones);
		if (!rag_instanciate(sko, doll, emat, wed))
		{
			rag_uninstanciate(sko);
			Con_DPrintf("PF_skel_ragedit: unable to instanciate objects\n");
			G_FLOAT(OFS_RETURN) = 0;
			return;
		}
		if (sko->numanimated)
			rag_animate(sko, doll, emat);
	}
	else if (!doll)
	{
		G_FLOAT(OFS_RETURN) = 1;	//technically success.
		return;
	}
	else if (sko->numanimated)
	{
		skel_copy_toabs(sko, psko?psko:sko, 0, sko->numbones);
		rag_animate(sko, doll, emat);
	}

	rag_derive(sko, psko, emat);
	G_FLOAT(OFS_RETURN) = 1;
#endif
}

//float(float modelindex) skel_create (FTE_CSQC_SKELETONOBJECTS)
void QCBUILTIN PF_skel_create (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	world_t *w = prinst->parms->user;

	int numbones;
	skelobject_t *skelobj;
	model_t *model;
	int midx;
	int type;

	midx = G_FLOAT(OFS_PARM0);
	type = (prinst->callargc > 1)?G_FLOAT(OFS_PARM1):SKOT_RELATIVE;

	//default to failure
	G_FLOAT(OFS_RETURN) = 0;

	model = w->Get_CModel(w, midx);
	if (!model)
		return; //no model set, can't get a skeleton

	numbones = Mod_GetNumBones(model, type != SKOT_RELATIVE);
	if (!numbones)
	{
//		isabs = true;
//		numbones = Mod_GetNumBones(model, isabs);
//		if (!numbones)
			return;	//this isn't a skeletal model.
	}

	skelobj = skel_create(prinst, numbones);
	if (!skelobj)
		return;	//couldn't get one, ran out of memory or something?

	skelobj->model = model;
	skelobj->type = type;

	/*
	for (i = 0; i < numbones; i++)
	{
		galiasbone_t *bones = Mod_GetBoneInfo(skelobj->model);
		Matrix3x4_Invert_Simple(bones[i].inverse, skelobj->bonematrix + i*12);
	}
	skelobj->type = SKOT_ABSOLUTE;
	*/

	G_FLOAT(OFS_RETURN) = (skelobj - skelobjects) + 1;
}

//float(float skel, entity ent, float modelindex, float retainfrac, float firstbone, float lastbone) skel_build (FTE_CSQC_SKELETONOBJECTS)
void QCBUILTIN PF_skel_build(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	world_t *w = prinst->parms->user;
	int skelidx = G_FLOAT(OFS_PARM0);
	wedict_t *ent = (wedict_t*)G_EDICT(prinst, OFS_PARM1);
	int midx = G_FLOAT(OFS_PARM2);
	float retainfrac = G_FLOAT(OFS_PARM3);
	int firstbone = G_FLOAT(OFS_PARM4)-1;
	int lastbone = G_FLOAT(OFS_PARM5)-1;
	float addition = (prinst->callargc>6)?G_FLOAT(OFS_PARM6):1-retainfrac;

	int i, j;
	int numbones;
	framestate_t fstate;
	skelobject_t *skelobj;
	model_t *model;

	//default to failure
	G_FLOAT(OFS_RETURN) = 0;

	model = w->Get_CModel(w, midx);
	if (!model)
		return; //invalid model, can't get a skeleton

	w->Get_FrameState(w, ent, &fstate);

	//heh... don't copy.
	fstate.bonecount = 0;
	fstate.bonestate = NULL;

	numbones = Mod_GetNumBones(model, false);
	if (!numbones)
	{
		return;	//this isn't a skeletal model.
	}

	if (!skelidx)
		skelobj = skel_create(prinst, numbones);
	else
		skelobj = skel_get(prinst, skelidx);
	if (!skelobj)
		return;	//couldn't get one, ran out of memory or something?

	if (lastbone < 0)
		lastbone = numbones;
	if (lastbone > numbones)
		lastbone = numbones;
	if (firstbone < 0)
		firstbone = 0;
	if (lastbone < firstbone)
		lastbone = firstbone;

	if (skelobj->type != SKOT_RELATIVE)
	{
		if (firstbone > 0 || lastbone < skelobj->numbones || retainfrac)
		{
			Con_Printf("skel_build on non-relative skeleton\n");
			return;
		}
		skelobj->type = SKOT_RELATIVE;	//entire model will get replaced, convert it.
	}

	if (retainfrac == 0)
	{
		if (addition == 1) /*replace everything*/
			Mod_GetBoneRelations(model, firstbone, lastbone, &fstate, skelobj->bonematrix);
		else if (addition == 0) /*wipe it*/
			memset(skelobj->bonematrix + firstbone*12, 0, sizeof(float)*12*(lastbone-firstbone));
		else
		{
			//scale new
			float relationsbuf[MAX_BONES*12];
			Mod_GetBoneRelations(model, firstbone, lastbone, &fstate, relationsbuf);
			for (i = firstbone; i < lastbone; i++)
			{
				for (j = 0; j < 12; j++)
					skelobj->bonematrix[i*12+j] = addition*relationsbuf[i*12+j];
			}
		}
	}
	else
	{
		if (retainfrac != 1)
		{
			//rescale the existing bones
			for (i = firstbone; i < lastbone; i++)
			{
				for (j = 0; j < 12; j++)
					skelobj->bonematrix[i*12+j] *= retainfrac;
			}
		}
		if (addition == 1)
		{
			//just add
			float relationsbuf[MAX_BONES*12];
			Mod_GetBoneRelations(model, firstbone, lastbone, &fstate, relationsbuf);
			for (i = firstbone; i < lastbone; i++)
			{
				for (j = 0; j < 12; j++)
					skelobj->bonematrix[i*12+j] += relationsbuf[i*12+j];
			}
		}
		else if (addition)
		{
			//add+scale
			float relationsbuf[MAX_BONES*12];
			Mod_GetBoneRelations(model, firstbone, lastbone, &fstate, relationsbuf);
			for (i = firstbone; i < lastbone; i++)
			{
				for (j = 0; j < 12; j++)
					skelobj->bonematrix[i*12+j] += addition*relationsbuf[i*12+j];
			}
		}
	}

	G_FLOAT(OFS_RETURN) = (skelobj - skelobjects) + 1;
}

//float(float skel) skel_get_numbones (FTE_CSQC_SKELETONOBJECTS)
void QCBUILTIN PF_skel_get_numbones (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	int skelidx = G_FLOAT(OFS_PARM0);
	skelobject_t *skelobj;

	skelobj = skel_get(prinst, skelidx);

	if (!skelobj)
		G_FLOAT(OFS_RETURN) = 0;
	else
		G_FLOAT(OFS_RETURN) = skelobj->numbones;
}

//string(float skel, float bonenum) skel_get_bonename (FTE_CSQC_SKELETONOBJECTS) (returns tempstring)
void QCBUILTIN PF_skel_get_bonename (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	int skelidx = G_FLOAT(OFS_PARM0);
	int boneidx = G_FLOAT(OFS_PARM1);
	skelobject_t *skelobj;

	skelobj = skel_get(prinst, skelidx);

	if (!skelobj)
		G_INT(OFS_RETURN) = 0;
	else
	{
		RETURN_TSTRING(Mod_GetBoneName(skelobj->model, boneidx));
	}
}

//float(float skel, float bonenum) skel_get_boneparent (FTE_CSQC_SKELETONOBJECTS)
void QCBUILTIN PF_skel_get_boneparent (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	int skelidx = G_FLOAT(OFS_PARM0);
	int boneidx = G_FLOAT(OFS_PARM1);
	skelobject_t *skelobj;

	skelobj = skel_get(prinst, skelidx);

	if (!skelobj)
		G_FLOAT(OFS_RETURN) = 0;
	else
		G_FLOAT(OFS_RETURN) = Mod_GetBoneParent(skelobj->model, boneidx);
}

//float(float skel, string tagname) skel_find_bone (FTE_CSQC_SKELETONOBJECTS)
void QCBUILTIN PF_skel_find_bone (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	int skelidx = G_FLOAT(OFS_PARM0);
	char *bname = PR_GetStringOfs(prinst, OFS_PARM1);
	skelobject_t *skelobj;

	skelobj = skel_get(prinst, skelidx);
	if (!skelobj)
		G_FLOAT(OFS_RETURN) = 0;
	else
		G_FLOAT(OFS_RETURN) = Mod_TagNumForName(skelobj->model, bname);
}

//vector(float skel, float bonenum) skel_get_bonerel (FTE_CSQC_SKELETONOBJECTS) (sets v_forward etc)
void QCBUILTIN PF_skel_get_bonerel (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	world_t *w = prinst->parms->user;
	int skelidx = G_FLOAT(OFS_PARM0);
	int boneidx = G_FLOAT(OFS_PARM1)-1;
	skelobject_t *skelobj = skel_get(prinst, skelidx);
	if (!skelobj || (unsigned int)boneidx >= skelobj->numbones)
		bonematident_toqcvectors(w->g.v_forward, w->g.v_right, w->g.v_up, G_VECTOR(OFS_RETURN));
	else if (skelobj->type!=SKOT_RELATIVE)
	{
		float tmp[12];
		float invparent[12];
		int parent;
		/*invert the parent, multiply that against the child, we now know the transform required to go from parent to child. woo.*/
		parent = Mod_GetBoneParent(skelobj->model, boneidx+1)-1;
		Matrix3x4_Invert(skelobj->bonematrix+12*parent, invparent);
		Matrix3x4_Multiply(invparent, skelobj->bonematrix+12*boneidx, tmp);
		bonemat_toqcvectors(tmp, w->g.v_forward, w->g.v_right, w->g.v_up, G_VECTOR(OFS_RETURN));
	}
	else
		bonemat_toqcvectors(skelobj->bonematrix+12*boneidx, w->g.v_forward, w->g.v_right, w->g.v_up, G_VECTOR(OFS_RETURN));
}

//vector(float skel, float bonenum) skel_get_boneabs (FTE_CSQC_SKELETONOBJECTS) (sets v_forward etc)
void QCBUILTIN PF_skel_get_boneabs (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	world_t *w = prinst->parms->user;
	int skelidx = G_FLOAT(OFS_PARM0);
	int boneidx = G_FLOAT(OFS_PARM1)-1;
	float workingm[12], tempmatrix[3][4];
	int i;
	skelobject_t *skelobj = skel_get(prinst, skelidx);

	if (!skelobj || (unsigned int)boneidx >= skelobj->numbones)
		bonematident_toqcvectors(w->g.v_forward, w->g.v_right, w->g.v_up, G_VECTOR(OFS_RETURN));
	else if (skelobj->type != SKOT_RELATIVE)
	{
		//can just copy it out
		bonemat_toqcvectors(skelobj->bonematrix + boneidx*12, w->g.v_forward, w->g.v_right, w->g.v_up, G_VECTOR(OFS_RETURN));
	}
	else
	{
		//we need to work out the abs position

		//testme

		//set up an identity matrix
		for (i = 0;i < 12;i++)
			workingm[i] = 0;
		workingm[0] = 1;
		workingm[5] = 1;
		workingm[10] = 1;

		while(boneidx >= 0)
		{
			//copy out the previous working matrix, so we don't stomp on it
			memcpy(tempmatrix, workingm, sizeof(tempmatrix));
			R_ConcatTransforms((void*)(skelobj->bonematrix + boneidx*12), (void*)tempmatrix, (void*)workingm);

			boneidx = Mod_GetBoneParent(skelobj->model, boneidx+1)-1;
		}
		bonemat_toqcvectors(workingm, w->g.v_forward, w->g.v_right, w->g.v_up, G_VECTOR(OFS_RETURN));
	}
}

//void(entity ent, float bonenum, vector org, optional fwd, right, up) skel_set_bone_world (FTE_CSQC_SKELETONOBJECTS2) (reads v_forward etc)
void QCBUILTIN PF_skel_set_bone_world (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	world_t *w = prinst->parms->user;
	wedict_t *ent = G_WEDICT(prinst, OFS_PARM0);
	unsigned int boneidx = G_FLOAT(OFS_PARM1)-1;
	float *matrix[3];
	skelobject_t *skelobj;
	float *bone;
	float childworld[12], parentinv[12];

	/*sort out the parameters*/
	if (prinst->callargc == 4)
	{
		vec3_t d[3], a;
		a[0] = G_VECTOR(OFS_PARM3)[0] * -1; /*mod_alias bug*/
		a[1] = G_VECTOR(OFS_PARM3)[1];
		a[2] = G_VECTOR(OFS_PARM3)[2];
		AngleVectors(a, d[0], d[1], d[2]);
		bonemat_fromqcvectors(childworld, d[0], d[1], d[2], G_VECTOR(OFS_PARM2));
	}
	else
	{
		if (prinst->callargc > 5)
		{
			matrix[0] = G_VECTOR(OFS_PARM3);
			matrix[1] = G_VECTOR(OFS_PARM4);
			matrix[2] = G_VECTOR(OFS_PARM5);
		}
		else
		{
			matrix[0] = w->g.v_forward;
			matrix[1] = w->g.v_right;
			matrix[2] = w->g.v_up;
		}
		bonemat_fromqcvectors(childworld, matrix[0], matrix[1], matrix[2], G_VECTOR(OFS_PARM2));
	}

	/*make sure the skeletal object is correct*/
	skelobj = skel_get(prinst, ent->xv->skeletonindex);
	if (!skelobj || boneidx >= skelobj->numbones)
		return;

	/*get the inverse of the parent matrix*/
	{
		float parentabs[12];
		float parentw[12];
		float parentent[12];
		framestate_t fstate;
		w->Get_FrameState(w, ent, &fstate);
		if (skelobj->type == SKOT_ABSOLUTE || !Mod_GetTag(skelobj->model, Mod_GetBoneParent(skelobj->model, boneidx+1), &fstate, parentabs))
		{
			bonemat_fromentity(w, ent, parentw);
		}
		else
		{
			bonemat_fromentity(w, ent, parentent);
			Matrix3x4_Multiply(parentabs, parentent, parentw);
		}
		Matrix3x4_Invert(parentw, parentinv);
	}

	/*calc the result*/
	bone = skelobj->bonematrix+12*boneidx;
	Matrix3x4_Multiply(childworld, parentinv, bone);
}

//void(float skel, float bonenum, vector org) skel_set_bone (FTE_CSQC_SKELETONOBJECTS) (reads v_forward etc)
void QCBUILTIN PF_skel_set_bone (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	world_t *w = prinst->parms->user;
	int skelidx = G_FLOAT(OFS_PARM0);
	unsigned int boneidx = G_FLOAT(OFS_PARM1)-1;
	float *matrix[3];
	skelobject_t *skelobj;
	float *bone;

	if (prinst->callargc > 5)
	{
		matrix[0] = G_VECTOR(OFS_PARM3);
		matrix[1] = G_VECTOR(OFS_PARM4);
		matrix[2] = G_VECTOR(OFS_PARM5);
	}
	else
	{
		matrix[0] = w->g.v_forward;
		matrix[1] = w->g.v_right;
		matrix[2] = w->g.v_up;
	}

	skelobj = skel_get(prinst, skelidx);
	if (!skelobj || boneidx >= skelobj->numbones)
		return;

	bone = skelobj->bonematrix+12*boneidx;
	bonemat_fromqcvectors(bone, matrix[0], matrix[1], matrix[2], G_VECTOR(OFS_PARM2));
}

//void(float skel, float bonenum, vector org [, vector fwd, vector right, vector up]) skel_mul_bone (FTE_CSQC_SKELETONOBJECTS) (reads v_forward etc)
void QCBUILTIN PF_skel_mul_bone (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	world_t *w = prinst->parms->user;
	int skelidx = G_FLOAT(OFS_PARM0);
	int boneidx = G_FLOAT(OFS_PARM1)-1;
	float temp[3][4];
	float mult[3][4];
	skelobject_t *skelobj;
	if (prinst->callargc > 5)
		bonemat_fromqcvectors((float*)mult, G_VECTOR(OFS_PARM3), G_VECTOR(OFS_PARM4), G_VECTOR(OFS_PARM5), G_VECTOR(OFS_PARM2));
	else
		bonemat_fromqcvectors((float*)mult, w->g.v_forward, w->g.v_right, w->g.v_up, G_VECTOR(OFS_PARM2));

	skelobj = skel_get(prinst, skelidx);
	if (!skelobj || boneidx >= skelobj->numbones)
		return;
//testme
	Vector4Copy(skelobj->bonematrix+12*boneidx+0, temp[0]);
	Vector4Copy(skelobj->bonematrix+12*boneidx+4, temp[1]);
	Vector4Copy(skelobj->bonematrix+12*boneidx+8, temp[2]);
	R_ConcatTransforms(mult, temp, (float(*)[4])(skelobj->bonematrix+12*boneidx));
}

//void(float skel, float startbone, float endbone, vector org) skel_mul_bone (FTE_CSQC_SKELETONOBJECTS) (reads v_forward etc)
void QCBUILTIN PF_skel_mul_bones (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	world_t *w = prinst->parms->user;
	int skelidx = G_FLOAT(OFS_PARM0);
	unsigned int startbone = G_FLOAT(OFS_PARM1)-1;
	unsigned int endbone = G_FLOAT(OFS_PARM2)-1;
	float temp[3][4];
	float mult[3][4];
	skelobject_t *skelobj;
	if (prinst->callargc > 6)
		bonemat_fromqcvectors((float*)mult, G_VECTOR(OFS_PARM4), G_VECTOR(OFS_PARM5), G_VECTOR(OFS_PARM6), G_VECTOR(OFS_PARM3));
	else
		bonemat_fromqcvectors((float*)mult, w->g.v_forward, w->g.v_right, w->g.v_up, G_VECTOR(OFS_PARM3));

	skelobj = skel_get(prinst, skelidx);
	if (!skelobj)
		return;

	if (startbone == -1)
		startbone = 0;
	if (endbone == -1)
		endbone = skelobj->numbones;
	else if (endbone > skelobj->numbones)
		endbone = skelobj->numbones;

	while(startbone < endbone)
	{
		Vector4Copy(skelobj->bonematrix+12*startbone+0, temp[0]);
		Vector4Copy(skelobj->bonematrix+12*startbone+4, temp[1]);
		Vector4Copy(skelobj->bonematrix+12*startbone+8, temp[2]);
		R_ConcatTransforms(mult, temp, (float(*)[4])(skelobj->bonematrix+12*startbone));

		startbone++;
	}
}

//void(float skeldst, float skelsrc, float startbone, float entbone) skel_copybones (FTE_CSQC_SKELETONOBJECTS)
void QCBUILTIN PF_skel_copybones (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	int skeldst = G_FLOAT(OFS_PARM0);
	int skelsrc = G_FLOAT(OFS_PARM1);
	int startbone = G_FLOAT(OFS_PARM2)-1;
	int endbone = G_FLOAT(OFS_PARM3)-1;

	skelobject_t *skelobjdst;
	skelobject_t *skelobjsrc;

	skelobjdst = skel_get(prinst, skeldst);
	skelobjsrc = skel_get(prinst, skelsrc);
	if (!skelobjdst || !skelobjsrc)
		return;
	if (startbone == -1)
		startbone = 0;
	if (endbone == -1)
		endbone = skelobjdst->numbones;
	if (endbone > skelobjdst->numbones)
		endbone = skelobjdst->numbones;
	if (endbone > skelobjsrc->numbones)
		endbone = skelobjsrc->numbones;

	if (skelobjsrc->type == skelobjdst->type)
	{
		while(startbone < endbone)
		{
			Vector4Copy(skelobjsrc->bonematrix+12*startbone+0, skelobjdst->bonematrix+12*startbone+0);
			Vector4Copy(skelobjsrc->bonematrix+12*startbone+4, skelobjdst->bonematrix+12*startbone+4);
			Vector4Copy(skelobjsrc->bonematrix+12*startbone+8, skelobjdst->bonematrix+12*startbone+8);

			startbone++;
		}
	}
	else if (skelobjsrc->type == SKOT_RELATIVE && skelobjdst->type == SKOT_ABSOLUTE)
	{
		/*copy from relative to absolute*/
		skel_copy_toabs(skelobjdst, skelobjsrc, startbone, endbone);
	}
	else
	{
		/*copy from absolute to relative*/
		//FIXME
	}
}

//void(float skel) skel_delete (FTE_CSQC_SKELETONOBJECTS)
void QCBUILTIN PF_skel_delete (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	int skelidx = G_FLOAT(OFS_PARM0);
	skelobject_t *skelobj;

	skelobj = skel_get(prinst, skelidx);
	if (skelobj)
	{
		skelobj->inuse = 2;	//2 means don't reuse yet.
		skelobj->model = NULL;
		pendingkill = true;
	}
}

//vector(entity ent, float tag) gettaginfo (DP_MD3_TAGSINFO)
void QCBUILTIN PF_gettaginfo (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	world_t *w = prinst->parms->user;
	wedict_t *ent = G_WEDICT(prinst, OFS_PARM0);
	int tagnum = G_FLOAT(OFS_PARM1);

	int modelindex = ent->v->modelindex;

	model_t *mod = w->Get_CModel(w, modelindex);

	float transent[12];
	float transforms[12];
	float result[12];

	framestate_t fstate;

	w->Get_FrameState(w, ent, &fstate);

	if (!Mod_GetTag(mod, tagnum, &fstate, transforms))
	{
		bonemat_fromidentity(transforms);
	}

	if (ent->xv->tag_entity)
	{
#ifdef warningmsg
		#pragma warningmsg("PF_gettaginfo: This function doesn't honour attachments")
#endif
		Con_Printf("bug: PF_gettaginfo doesn't support attachments\n");
	}

	bonemat_fromentity(w, ent, transent);
	R_ConcatTransforms((void*)transent, (void*)transforms, (void*)result);

	bonemat_toqcvectors(result, w->g.v_forward, w->g.v_right, w->g.v_up, G_VECTOR(OFS_RETURN));
}

//vector(entity ent, string tagname) gettagindex (DP_MD3_TAGSINFO)
void QCBUILTIN PF_gettagindex (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	world_t *w = prinst->parms->user;
	wedict_t *ent = G_WEDICT(prinst, OFS_PARM0);
	char *tagname = PR_GetStringOfs(prinst, OFS_PARM1);
	model_t *mod = *tagname?w->Get_CModel(w, ent->v->modelindex):NULL;
	if (mod)
		G_FLOAT(OFS_RETURN) = Mod_TagNumForName(mod, tagname);
	else
		G_FLOAT(OFS_RETURN) = 0;
}

const char *Mod_FrameNameForNum(model_t *model, int num);
const char *Mod_SkinNameForNum(model_t *model, int num);

//string(float modidx, float framenum) frametoname
void QCBUILTIN PF_frametoname (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	world_t *w = prinst->parms->user;
	unsigned int modelindex = G_FLOAT(OFS_PARM0);
	unsigned int skinnum = G_FLOAT(OFS_PARM1);
	model_t *mod = w->Get_CModel(w, modelindex);
	const char *n = Mod_FrameNameForNum(mod, skinnum);

	if (n)
		RETURN_TSTRING(n);
	else
		G_INT(OFS_RETURN) = 0;	//null string (which is also empty in qc)
}

//string(float modidx, float skinnum) skintoname
void QCBUILTIN PF_skintoname (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	world_t *w = prinst->parms->user;
	unsigned int modelindex = G_FLOAT(OFS_PARM0);
	unsigned int skinnum = G_FLOAT(OFS_PARM1);
	model_t *mod = w->Get_CModel(w, modelindex);
	const char *n = Mod_SkinNameForNum(mod, skinnum);

	if (n)
		RETURN_TSTRING(n);
	else
		G_INT(OFS_RETURN) = 0;	//null string (which is also empty in qc)
}

void QCBUILTIN PF_frameforname (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	world_t *w = prinst->parms->user;
	unsigned int modelindex = G_FLOAT(OFS_PARM0);
	char *str = PF_VarString(prinst, 1, pr_globals);
	model_t *mod = w->Get_CModel(w, modelindex);

	if (mod && Mod_FrameForName)
		G_FLOAT(OFS_RETURN) = Mod_FrameForName(mod, str);
	else
		G_FLOAT(OFS_RETURN) = -1;
}
void QCBUILTIN PF_frameduration (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
	world_t *w = prinst->parms->user;
	unsigned int modelindex = G_FLOAT(OFS_PARM0);
	unsigned int framenum = G_FLOAT(OFS_PARM1);
	model_t *mod = w->Get_CModel(w, modelindex);

	if (mod && Mod_GetFrameDuration)
		G_FLOAT(OFS_RETURN) = Mod_GetFrameDuration(mod, framenum);
	else
		G_FLOAT(OFS_RETURN) = 0;
}
void QCBUILTIN PF_skinforname (pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
#ifndef SERVERONLY
	world_t *w = prinst->parms->user;
	unsigned int modelindex = G_FLOAT(OFS_PARM0);
	char *str = PF_VarString(prinst, 1, pr_globals);
	model_t *mod = w->Get_CModel(w, modelindex);

	if (mod && Mod_SkinForName)
		G_FLOAT(OFS_RETURN) = Mod_SkinForName(mod, str);
	else
#endif
		G_FLOAT(OFS_RETURN) = -1;
}
#endif

