#include "driver2.h"
#include "motion_c.h"
#include "pedest.h"
#include "system.h"
#include "mission.h"
#include "texture.h"
#include "camera.h"
#include "shadow.h"
#include "debris.h"
#include "draw.h"
#include "dr2roads.h"
#include "sky.h"
#include "players.h"
#include "main.h"

#include "cars.h"
#include "convert.h"

#ifdef USE_PGXP
#include <math.h>
#endif

enum LIMBS
{
	ROOT = 0,
	LOWERBACK = 1,
	JOINT_1 = 2,
	NECK = 3,
	HEAD = 4,
	LSHOULDER = 5,
	LELBOW = 6,
	LHAND = 7,
	LFINGERS = 8,
	RSHOULDER = 9,
	RELBOW = 10,
	RHAND = 11,
	RFINGERS = 12,
	HIPS = 13,
	LHIP = 14,
	LKNEE = 15,
	LFOOT = 16,
	LTOE = 17,
	RHIP = 18,
	RKNEE = 19,
	RFOOT = 20,
	RTOE = 21,
	JOINT = 22,
};

enum TEXTURE_PALS
{
	NO_PAL = 0,
	JEANS_PAL = 1,
	ARM_PAL = 2,
	CHEST_PAL = 3,
};

struct BONE
{
	LIMBS id;
	BONE* pParent;
	char numChildren;
	BONE(*pChildren[3]);
	SVECTOR_NOPAD* pvOrigPos;
	SVECTOR* pvRotation;
	VECTOR vOffset;
	VECTOR vCurrPos;
	MODEL** pModel;
};

struct PED_DATA
{
	char cWidth;
	u_char cAdj;
	TEXTURE_DETAILS* ptd;
	TEXTURE_PALS texPal;
};

#define NUM_BONES 23

LIMBS lRoutes[5][8] = {
	{
		ROOT,		LOWERBACK,
		HIPS,		LHIP,
		LKNEE,		LFOOT,
		LTOE,		ROOT
	},
	{
		HIPS,		RHIP,
		RKNEE,		RFOOT,
		RTOE,		ROOT,
		ROOT,		ROOT
	},
	{
		LOWERBACK,	JOINT_1,
		LSHOULDER,	LELBOW,
		LHAND,		LFINGERS,
		ROOT,		ROOT
	},
	{
		JOINT_1,	RSHOULDER,
		RELBOW,		RHAND,
		RFINGERS,	ROOT,
		ROOT,		ROOT
	},
	{
		JOINT_1,	NECK,
		HEAD,		ROOT,
		ROOT,		ROOT,
		ROOT,		ROOT
	},
};

PED_DATA MainPed[NUM_BONES] =
{
	{ 0, 68u, &chest1_texture, CHEST_PAL },   
	{ 1, 68u, &chest1_texture, CHEST_PAL },
	{ 17, 36u, &chest1_texture, CHEST_PAL },
	{ 6, 68u, &forearm1_texture, ARM_PAL },
	{ 8, 22u, &head1_texture, NO_PAL },
	{ 6, 68u, &arm1_texture, CHEST_PAL },
	{ 8, 36u, &arm1_texture, CHEST_PAL },
	{ 7, 68u, &forearm1_texture, ARM_PAL },
	{ 3, 244u, &forearm1_texture, ARM_PAL },
	{ 6, 68u, &arm1_texture, CHEST_PAL },
	{ 8, 36u, &arm1_texture, CHEST_PAL },
	{ 7, 68u, &forearm1_texture, ARM_PAL },
	{ 3, 244u, &forearm1_texture, ARM_PAL },
	{ 1, 68u, &jeans_texture, NO_PAL },
	{ 6, 68u, &jeans_texture, NO_PAL },
	{ 13, 100u, &jeans_texture, JEANS_PAL },
	{ 11, 244u, &jeans_texture, JEANS_PAL },
	{ 6, 63u, &chest1_texture, JEANS_PAL },
	{ 6, 68u, &arm1_texture, NO_PAL },
	{ 13, 100u, &jeans_texture, JEANS_PAL },
	{ 11, 244u, &jeans_texture, JEANS_PAL },
	{ 6, 63u, &chest1_texture, JEANS_PAL },
	{ 2, 68u, &jeans_texture, JEANS_PAL }
};


// FIXME: could be incorrect
BONE Skel[NUM_BONES] =
{
	{
		ROOT,
		NULL,
		2,
		{ &Skel[LOWERBACK], &Skel[HIPS], NULL },
		NULL,
		NULL,
		{ 0, 0, 0, 0 },
		{ 0, 0, 0, 0 },
		NULL
	},
	{
		LOWERBACK,
		&Skel[ROOT],
		3,
		{ &Skel[JOINT_1], &Skel[LHIP], &Skel[RHIP] },
		NULL,
		NULL,
		{ 0, 0, 0, 0 },
		{ 0, 0, 0, 0 },
		NULL
	},
	{
		JOINT_1,
		&Skel[LOWERBACK],
		3,
		{ &Skel[NECK], &Skel[LSHOULDER], &Skel[RSHOULDER] },
		NULL,
		NULL,
		{ 0, 0, 0, 0 },
		{ 0, 0, 0, 0 },
		&pmTannerModels[0]
	},
	{
		NECK,
		&Skel[JOINT_1],
		1,
		{ &Skel[HEAD], NULL, NULL },
		NULL,
		NULL,
		{ 0, 0, 0, 0 },
		{ 0, 0, 0, 0 },
		&pmTannerModels[14]
	},
	{
		HEAD,
		&Skel[NECK],
		0,
		{ NULL, NULL, NULL },
		NULL,
		NULL,
		{ 0, 0, 0, 0 },
		{ 0, 0, 0, 0 },
		&pmTannerModels[1]
	},
	{
		LSHOULDER,
		&Skel[JOINT_1],
		1,
		{ &Skel[LELBOW], NULL, NULL },
		NULL,
		NULL,
		{ 0, 0, 0, 0 },
		{ 0, 0, 0, 0 },
		NULL
	},
	{
		LELBOW,
		&Skel[LSHOULDER],
		1,
		{ &Skel[LHAND], NULL, NULL },
		NULL,
		NULL,
		{ 0, 0, 0, 0 },
		{ 0, 0, 0, 0 },
		&pmTannerModels[8]
	},
	{
		LHAND,
		&Skel[LELBOW],
		1,
		{ &Skel[LFINGERS], NULL, NULL },
		NULL,
		NULL,
		{ 0, 0, 0, 0 },
		{ 0, 0, 0, 0 },
		&pmTannerModels[9]
	},
	{
		LFINGERS,
		&Skel[LHAND],
		0,
		{ NULL, NULL, NULL },
		NULL,
		NULL,
		{ 0, 0, 0, 0 },
		{ 0, 0, 0, 0 },
		&pmTannerModels[10]
	},
	{
		RSHOULDER,
		&Skel[JOINT_1],
		1,
		{ &Skel[RELBOW], NULL, NULL },
		NULL,
		NULL,
		{ 0, 0, 0, 0 },
		{ 0, 0, 0, 0 },
		NULL
	},
	{
		RELBOW,
		&Skel[RSHOULDER],
		1,
		{ &Skel[RHAND], NULL, NULL },
		NULL,
		NULL,
		{ 0, 0, 0, 0 },
		{ 0, 0, 0, 0 },
		&pmTannerModels[2]
	},
	{
		RHAND,
		&Skel[RELBOW],
		1,
		{ &Skel[RFINGERS], NULL, NULL },
		NULL,
		NULL,
		{ 0, 0, 0, 0 },
		{ 0, 0, 0, 0 },
		&pmTannerModels[3]
	},
	{
		RFINGERS,
		&Skel[RHAND],
		0,
		{ NULL, NULL, NULL },
		NULL,
		NULL,
		{ 0, 0, 0, 0 },
		{ 0, 0, 0, 0 },
		&pmTannerModels[4]
	},
	{
		HIPS,
		&Skel[ROOT],
		2,
		{ &Skel[RHIP], &Skel[LHIP], NULL },
		NULL,
		NULL,
		{ 0, 0, 0, 0 },
		{ 0, 0, 0, 0 },
		NULL
	},
	{
		LHIP,
		&Skel[HIPS],
		1,
		{ &Skel[LKNEE], NULL, NULL },
		NULL,
		NULL,
		{ 0, 0, 0, 0 },
		{ 0, 0, 0, 0 },
		&pmTannerModels[15]
	},
	{
		LKNEE,
		&Skel[LHIP],
		1,
		{ &Skel[LFOOT], NULL, NULL },
		NULL,
		NULL,
		{ 0, 0, 0, 0 },
		{ 0, 0, 0, 0 },
		&pmTannerModels[11]
	},
	{
		LFOOT,
		&Skel[LKNEE],
		1,
		{ &Skel[LTOE], NULL, NULL },
		NULL,
		NULL,
		{ 0, 0, 0, 0 },
		{ 0, 0, 0, 0 },
		&pmTannerModels[12]
	},
	{
		LTOE,
		&Skel[LFOOT],
		0,
		{ NULL, NULL, NULL },
		NULL,
		NULL,
		{ 0, 0, 0, 0 },
		{ 0, 0, 0, 0 },
		&pmTannerModels[13]
	},
	{
		RHIP,
		&Skel[HIPS],
		1,
		{ &Skel[RKNEE], NULL, NULL },
		NULL,
		NULL,
		{ 0, 0, 0, 0 },
		{ 0, 0, 0, 0 },
		NULL
	},
	{
		RKNEE,
		&Skel[RHIP],
		1,
		{ &Skel[RFOOT], NULL, NULL },
		NULL,
		NULL,
		{ 0, 0, 0, 0 },
		{ 0, 0, 0, 0 },
		&pmTannerModels[5]
	},
	{
		RFOOT,
		&Skel[RKNEE],
		1,
		{ &Skel[RTOE], NULL, NULL },
		NULL,
		NULL,
		{ 0, 0, 0, 0 },
		{ 0, 0, 0, 0 },
		&pmTannerModels[6]
	},
	{
		RTOE,
		&Skel[RFOOT],
		0,
		{ NULL, NULL, NULL },
		NULL,
		NULL,
		{ 0, 0, 0, 0 },
		{ 0, 0, 0, 0 },
		&pmTannerModels[7]
	},
	{
		JOINT,
		&Skel[LOWERBACK],
		0,
		{ NULL, NULL, NULL },
		NULL,
		NULL,
		{ 0, 0, 0, 0 },
		{ 0, 0, 0, 0 },
		NULL
	}
};

int boneIdvals[] = {
	// 1
	JOINT_1,
	NECK,
	HEAD,

	// 2
	LELBOW,
	LHAND,
	LFINGERS,

	// 3
	RELBOW,
	RHAND,
	RFINGERS,

	// 4
	LKNEE,
	LFOOT,
	LTOE,

	// 5
	RKNEE,
	RFOOT,
	RTOE
};

TEXTURE_DETAILS jeans_texture;
TEXTURE_DETAILS arm1_texture;
TEXTURE_DETAILS forearm1_texture;
TEXTURE_DETAILS chest1_texture;
TEXTURE_DETAILS head1_texture;

MODEL* gPed1HeadModelPtr;
MODEL* gPed2HeadModelPtr;
MODEL* gPed3HeadModelPtr;
MODEL* gPed4HeadModelPtr;

char* MotionCaptureData[MAX_MOTION_CAPTURE];	// [A] actually, pointers
int ThisMotion;

short cTannerVNumbers[24];
SVECTOR vTannerList[210];

short cJerichoVNumbers[7];
SVECTOR vJerichoList[102];

int vStored = 0;

// [D] [T]
void ProcessMotionLump(char* lump_ptr, int lump_size)
{
	if (ThisMotion < MAX_MOTION_CAPTURE)
	{
		int size = (lump_size + 3) & ~3;

		D_MALLOC_BEGIN();
		MotionCaptureData[ThisMotion] = D_MALLOC(size);
		D_MALLOC_END();

		memcpy((u_char*)MotionCaptureData[ThisMotion], (u_char*)lump_ptr, size);

		ThisMotion++;
	}
}

// [D] [T]
void SetupPedMotionData(LPPEDESTRIAN pPed)
{
	pPed->motion = MotionCaptureData[pPed->type];
}


// [D] [T]
void SetupPedestrian(LPPEDESTRIAN pedptr)
{
	pedptr->velocity.vy = 10;
	pedptr->speed = 10;

	pedptr->dir.vx = 0;
	pedptr->dir.vz = 0;

	pedptr->frame1 = 0;
	pedptr->motion = MotionCaptureData[pedptr->type];
}

int bDoingShadow = 0;
int gCurrentZ;

// [D] [T] [A]
void DrawBodySprite(LPPEDESTRIAN pDrawingPed, int boneId, VERTTYPE v1[2], VERTTYPE v2[2], int sz, int sy)
{
#if 0
	// debug code
	{
		LINE_F2* line = (LINE_F2*)current->primptr;
		setLineF2(line);
		setSemiTrans(line, 1);

		line->x0 = v1[0];
		line->y0 = v1[1];
		line->x1 = v2[0];
		line->y1 = v2[1];

		line->r0 = 255;
		line->g0 = 0;
		line->b0 = 0;

		addPrim(current->ot + 2, line);
		current->primptr += sizeof(LINE_F2);
	}
#endif

	int clut;
	int angle;
	char tmp;
	int tmp2;
	int dx1, dy1,dx2,dy2;
	int width;
	POLY_FT4* prims;
	LIMBS bone;
	TEXTURE_DETAILS* body_texture;
	int x, y, z, z2;
	int tpage;
	int pal;
	int cs, sn;

	bone = (LIMBS)(boneId & 0x7f);
	body_texture = MainPed[bone].ptd;
	
	if (bDoingShadow)
		z = sz + (scr_z / 2);
	else
		z = gCurrentZ + (scr_z / 2);

	z2 = ((scr_z * 4096) / z) * 25 >> 5;

	if (bone == JOINT_1)
	{
		// make ped thinner from side view
		u_int ang;
		ang = RCOS((pDrawingPed->dir.vy + camera_angle.vy) * 2) >> 6;

		z2 += ang;
	}

	if (pDrawingPed->type == PED_ACTION_JUMP)
	{
		width = MainPed[bone].cWidth + 4;
	}
	else
	{
		if (pDrawingPed->flags & 0x8000)
			width = MainPed[bone].cWidth - 3;
		else if (pDrawingPed->flags & 0x4000)
			width = MainPed[bone].cWidth + 8;
		else
			width = MainPed[bone].cWidth + 3;
	}

	if (bDoingShadow &&
		(bone == RKNEE || bone == LKNEE || 
		bone == LFOOT || bone == RFOOT))
	{
		width -= 5;
	}

	x = v1[0] - v2[0];
	y = v1[1] - v2[1];

	angle = -ratan2(y, x);
	
	cs = FIXEDH(z2 * RSIN(angle) * (width & 0x3f) * 2);
	sn = FIXEDH(z2 * RCOS(angle) * (width & 0x3f) * 2);

	tmp = MainPed[bone].cAdj & 0xf;

	dx2 = x >> tmp;
	dy2 = y >> tmp;

	if ((bone == RKNEE || bone == LKNEE) && pDrawingPed->type != PED_ACTION_JUMP && bDoingShadow == 0)
	{
		dx1 = -x >> 3;
		dy1 = -y >> 3;
	}
	else
	{
		tmp2 = MainPed[bone].cAdj >> 4;
		
		dx1 = x >>tmp2;
		dy1 = y >> tmp2;
	}

	prims = (POLY_FT4*)current->primptr;
	setPolyFT4(prims);

	current->primptr += sizeof(POLY_FT4);

	prims->x0 = v1[0] + FIXEDH(cs) + dx2;
	prims->y0 = v1[1] + FIXEDH(sn) + dy2;

	prims->x1 = v1[0] - FIXEDH(cs) + dx2;
	prims->y1 = v1[1] - FIXEDH(sn) + dy2;

	prims->x2 = v2[0] + FIXEDH(cs) - dx1;
	prims->y2 = v2[1] + FIXEDH(sn) - dy1;

	prims->x3 = v2[0] - FIXEDH(cs) - dx1;
	prims->y3 = v2[1] - FIXEDH(sn) - dy1;

#ifdef USE_PGXP
	if (!bDoingShadow) // [A] Psy-X is currently incorrectly offsets the offscreen PGXP geometry. We don't need it anyway.
	{
		PGXPVData vdata1, vdata2;
		PGXP_GetCacheData(&vdata1, PGXP_LOOKUP_VALUE(v1[0], v1[1]), 0);
		PGXP_GetCacheData(&vdata2, PGXP_LOOKUP_VALUE(v2[0], v2[1]), 0);

		{
			float len;

			x = (v1[0] - v2[0]) * 4.0f;
			y = (v1[1] - v2[1]) * 4.0f;

			// compute normalization lengths
			len = 1.0 / sqrtf(float(x*x) + float(y*y) + 1.0);

			angle = -ratan2(y, x);

			sn = RSIN(angle) * width;
			cs = RCOS(angle) * width;

			tmp = MainPed[bone].cAdj & 0xf;

			dx2 = sn >> tmp;
			dy2 = cs >> tmp;

			if ((bone == RKNEE || bone == LKNEE) && pDrawingPed->type != PED_ACTION_JUMP && bDoingShadow == 0)
			{
				dx1 = -sn >> 3;
				dy1 = -cs >> 3;
			}
			else
			{
				tmp2 = MainPed[bone].cAdj >> 4;

				dx1 = sn >> tmp2;
				dy1 = cs >> tmp2;
			}

			dx1 = FIXED(dx1);
			dx2 = FIXED(dx2);
			dy1 = FIXED(dy1) + 2;
			dy2 = FIXED(dy2) - 2;

			if (bone == HEAD)
			{
				dx1 >>= 1;
				dx2 >>= 1;
			}

			if (bone == JOINT_1)
			{
				dx1 -= 5;
				dy2 -= 10;
			}
		}

		PGXPVData v0data = { PGXP_LOOKUP_VALUE(prims->x0, prims->y0),
			vdata1.px + (FIXEDH(sn) - dx1) * 0.01f,
			vdata1.py + (FIXEDH(cs) + dy1) * 0.01f,
			vdata1.pz, vdata1.scr_h, vdata1.ofx, vdata1.ofy };


		PGXPVData v1data = { PGXP_LOOKUP_VALUE(prims->x1, prims->y1),
			vdata1.px - (FIXEDH(sn) - dx1) * 0.01f,
			vdata1.py - (FIXEDH(cs) - dy1) * 0.01f,
			vdata1.pz, vdata1.scr_h, vdata1.ofx, vdata1.ofy };


		PGXPVData v2data = { PGXP_LOOKUP_VALUE(prims->x2, prims->y2),
			vdata2.px + (FIXEDH(sn) + dx2) * 0.01f,
			vdata2.py + (FIXEDH(cs) - dy2) * 0.01f,
			vdata2.pz, vdata2.scr_h, vdata2.ofx, vdata2.ofy };


		PGXPVData v3data = { PGXP_LOOKUP_VALUE(prims->x3, prims->y3),
			vdata2.px - (FIXEDH(sn) + dx2) * 0.01f,
			vdata2.py - (FIXEDH(cs) + dy2) * 0.01f,
			vdata2.pz, vdata2.scr_h, vdata2.ofx, vdata2.ofy };

		PGXP_EmitCacheData(&v0data);
		PGXP_EmitCacheData(&v1data);
		PGXP_EmitCacheData(&v2data);
		PGXP_EmitCacheData(&v3data);
	}
#endif

	if (bDoingShadow)
	{
		tpage = texture_pages[gShadowTexturePage];
		clut = texture_cluts[gShadowTexturePage][gShadowTextureNum];
	}
	else
	{
		tpage = body_texture->tpageid;
		clut = body_texture->clutid;

		if (MainPed[bone].texPal != NO_PAL)
		{
			pal = MainPed[bone].texPal == JEANS_PAL ? (pDrawingPed->pallet >> 4) : (pDrawingPed->pallet & 0xf);

			if (pal != 0)
				clut = civ_clut[0][body_texture->texture_number][pal];
		}
		else if(bone == HEAD)
		{
			pal = (pDrawingPed->pallet & 0xf);

			if (pal != 0)
				clut = civ_clut[0][body_texture->texture_number][pal];
		}
	}

	prims->tpage = tpage;
	prims->clut = clut;

	if (bDoingShadow)
	{
		setSemiTrans(prims, 1);
		prims->tpage |= 0x20;

		*(ushort*)&prims->u0 = *(ushort*)&shadowuv.u0;
		*(ushort*)&prims->u1 = *(ushort*)&shadowuv.u1;
		*(ushort*)&prims->u2 = *(ushort*)&shadowuv.u2;
		*(ushort*)&prims->u3 = *(ushort*)&shadowuv.u3;
	}
	else
	{
		if (bone == HEAD)
		{
			x = (-camera_angle.vy - pDrawingPed->dir.vy & 0xfffU) >> 7;

			prims->u0 = body_texture->coords.u0 + x;
			prims->v0 = body_texture->coords.v0;

			prims->u1 = body_texture->coords.u1 + x;
			prims->v1 = body_texture->coords.v1;

			prims->u2 = body_texture->coords.u2 + x;
			prims->v2 = body_texture->coords.v2;

			prims->u3 = body_texture->coords.u3 + x;
			prims->v3 = body_texture->coords.v3;
		}
		else if (bone == 2)
		{
			*(ushort*)&prims->u0 = *(ushort*)&body_texture->coords.u0;
			*(ushort*)&prims->u1 = *(ushort*)&body_texture->coords.u1;
			*(ushort*)&prims->u2 = *(ushort*)&body_texture->coords.u2;
			*(ushort*)&prims->u3 = *(ushort*)&body_texture->coords.u3;
		}
		else
		{
			*(ushort*)&prims->u0 = *(ushort*)&body_texture->coords.u2;
			*(ushort*)&prims->u1 = *(ushort*)&body_texture->coords.u3;
			*(ushort*)&prims->u2 = *(ushort*)&body_texture->coords.u0;
			*(ushort*)&prims->u3 = *(ushort*)&body_texture->coords.u1;
		}
	}

	if (bDoingShadow)
	{
		prims->r0 = 254;
		prims->g0 = 254;
		prims->b0 = 254;
	}
	else if (gNight)
	{
		prims->r0 = 64;
		prims->g0 = 64;
		prims->b0 = 64;
	}
	else
	{
		prims->r0 = (combointensity >> 0x10) & 0xFF;
		prims->g0 = (combointensity >> 8) & 0xFF;
		prims->b0 = combointensity & 0xFF;
	}

	if (bDoingShadow != 0)
	{
		addPrim(current->ot + OTSIZE - 1, prims);
	}
	else
	{
		x = sz + sy >> 4;
		addPrim(current->ot + x + (width >> 6), prims);
	}
}

// [D] [T]
void StoreVertexLists(void)
{
	BONE* pBone;
	SVECTOR* destVerts;
	SVECTOR* srcVerts;
	MODEL* pModel;
	int i,j,counter;

	// store Tanner vertices
	counter = 0;
	for (i = 0; i < 23; i++)
	{
		pBone = &Skel[i];

		if (pBone->pModel == NULL)
		{
			cTannerVNumbers[i] = -1;
			continue;
		}

		pModel = *pBone->pModel;

		if (pModel == NULL)
		{
			cTannerVNumbers[i] = -1;
			continue;
		}
		
		// store start index
		cTannerVNumbers[i] = counter;

		destVerts = &vTannerList[counter];
		srcVerts = (SVECTOR*)pModel->vertices;

		for (j = 0; j < pModel->num_vertices; j++)
		{
			destVerts->vx = srcVerts->vx;
			destVerts->vy = srcVerts->vy;
			destVerts->vz = srcVerts->vz;

			srcVerts++;
			destVerts++;
			counter++;
		}
	}

	// Store Jericho vertices
	counter = 0;
	for (i = 0; i < 6; i++)
	{
		pModel = pmJerichoModels[i];

		if (pModel == NULL)
		{
			cJerichoVNumbers[i] = -1;
			continue;
		}

		srcVerts = (SVECTOR*)pModel->vertices;

		// store start index
		cJerichoVNumbers[i] = counter;

		destVerts = &vJerichoList[counter];

		for (j = 0; j < pModel->num_vertices; j++)
		{
			destVerts->vx = srcVerts->vx;
			destVerts->vy = srcVerts->vy;
			destVerts->vz = srcVerts->vz;

			srcVerts++;
			destVerts++;
			counter++;
		}
	}

	vStored = 1;
}

#define BODY_OFFSET 8
#define TORSO_OFFSET -2

#define JERICHO_BODY_OFFSET 6
#define JERICHO_TORSO_OFFSET -5

int bodyShiftEnabled = 1;

int bodyShiftValue = BODY_OFFSET;
int torsoShiftValue = TORSO_OFFSET;

// [D] [T]
void SetupTannerSkeleton(LPPEDESTRIAN pDrawingPed)
{
	int i;
	BONE* pBone;
	char* pC;
	SVECTOR* store;
	SVECTOR_NOPAD* pSVNP;

	Skel[ROOT].pvOrigPos = (SVECTOR_NOPAD*)(pDrawingPed->motion + pDrawingPed->frame1 * 144 + 146);
	Skel[ROOT].pvRotation = (SVECTOR*)(pDrawingPed->motion + pDrawingPed->frame1 * 144 + 152);

	Skel[ROOT].vCurrPos.vx = Skel[ROOT].pvOrigPos->vx;
	Skel[ROOT].vCurrPos.vy = -Skel[ROOT].pvOrigPos->vy;
	Skel[ROOT].vCurrPos.vz = -Skel[ROOT].pvOrigPos->vz;

	Skel[ROOT].vOffset.vx = 0;
	Skel[ROOT].vOffset.vy = 0;
	Skel[ROOT].vOffset.vz = 0;

	pC = (pDrawingPed->motion + pDrawingPed->frame1 * 144 + 158);
	pSVNP = (SVECTOR_NOPAD*)(pDrawingPed->motion + 14);

	for (i = 1; i < NUM_BONES; i++)
	{
		pBone = &Skel[i];

		pBone->pvRotation = (SVECTOR*)pC;
		pBone->pvOrigPos = &pSVNP[i - 1];

		pC += sizeof(SVECTOR_NOPAD);
	}

#ifdef PSX
	store = (SVECTOR*)((u_char*)getScratchAddr(0) + 0x200);
#else
	SVECTOR scratchVectors[64];
	store = scratchVectors;
#endif

	store[LOWERBACK].vx = Skel[LOWERBACK].pvOrigPos->vx;
	store[LOWERBACK].vy = -Skel[LOWERBACK].pvOrigPos->vy;
	store[LOWERBACK].vz = -Skel[LOWERBACK].pvOrigPos->vz;

	// start at the torso and traverse down
	for (i = 2; i < NUM_BONES; i++)
	{
		pBone = &Skel[i];

		store[i].vx = pBone->pvOrigPos->vx - pBone->pParent->pvOrigPos->vx;
		store[i].vy = pBone->pParent->pvOrigPos->vy - pBone->pvOrigPos->vy;
		store[i].vz = pBone->pParent->pvOrigPos->vz - pBone->pvOrigPos->vz;

		if (pBone->id == JOINT_1 && bodyShiftEnabled)
		{
			store[i].vy -= bodyShiftValue;
			store[i].vz -= torsoShiftValue;
		}
	}

	// start at the lower back and traverse down
	for (i = 1; i < NUM_BONES; i++)
	{
		pBone = &Skel[i];

		pBone->vCurrPos.vx = store[i].vx;
		pBone->vOffset.vx = store[i].vx;

		pBone->vCurrPos.vy = store[i].vy;
		pBone->vOffset.vy = store[i].vy;

		pBone->vCurrPos.vz = store[i].vz;
		pBone->vOffset.vz = store[i].vz;
	}

#if 0
	// Draw T POSE
	{
		extern void Debug_AddLine(VECTOR & pointA, VECTOR & pointB, CVECTOR & color);
		extern void Debug_AddLineOfs(VECTOR & pointA, VECTOR & pointB, VECTOR & ofs, CVECTOR & color);

		CVECTOR bbcv = { 0, 0, 250 };
		CVECTOR rrcv = { 250, 0, 0 };
		CVECTOR yycv = { 250, 250, 0 };

		for (int i = 0; i < NUM_BONES; i++)
		{
			pBone = &Skel[i];

			for (int j = 0; j < pBone->numChildren; j++)
			{
				VECTOR v0 = {
					pBone->pvOrigPos->vx,
					pBone->pvOrigPos->vy,
					pBone->pvOrigPos->vz };

				VECTOR v1 = {
					pBone->pChildren[j]->pvOrigPos->vx,
					pBone->pChildren[j]->pvOrigPos->vy,
					pBone->pChildren[j]->pvOrigPos->vz };

				VECTOR ofs = *(VECTOR*)&pDrawingPed->position;
				ofs.vy = -ofs.vy;
				//ofs.vy += 270;

				Debug_AddLineOfs(v0, v1, ofs, bbcv);
			}

		}
	}
#endif
}

// [A] - was inlined in newShowTanner
void DrawSprite(LPPEDESTRIAN pDrawingPed, BONE* pBone, VECTOR* vJPos)
{
	VERTTYPE t0[2], t1[2]; // [A] was two longs
	int z, z1, z2;

#ifdef PSX
	SVECTOR* data = (SVECTOR*)((u_char*)getScratchAddr(0) + 0x200);
#else
	SVECTOR scratchVectors[64];
	SVECTOR* data = scratchVectors;
#endif

	data[0].vx = vJPos[pBone->id & 0x7f].vx + pDrawingPed->position.vx - camera_position.vx;
	data[0].vy = vJPos[pBone->id & 0x7f].vy + pDrawingPed->position.vy - camera_position.vy;
	data[0].vz = vJPos[pBone->id & 0x7f].vz + pDrawingPed->position.vz - camera_position.vz;

	data[1].vx = vJPos[pBone->pParent->id & 0x7f].vx + pDrawingPed->position.vx - camera_position.vx;
	data[1].vy = vJPos[pBone->pParent->id & 0x7f].vy + pDrawingPed->position.vy - camera_position.vy;
	data[1].vz = vJPos[pBone->pParent->id & 0x7f].vz + pDrawingPed->position.vz - camera_position.vz;

	gte_ldv0(&data[0]);
	gte_ldv1(&data[1]);

	gte_rtpt();
	gte_stsxy0(t0);
	gte_stsxy1(t1);

	gte_stsz3(&z, &z1, &z2);

	gCurrentZ = z;

	DrawBodySprite(pDrawingPed, pBone->id, t0, t1, z, z1);
}

int bAllreadyRotated = 0;

// [D] [T]
void newShowTanner(LPPEDESTRIAN pDrawingPed)
{
	int i, j;
	int draw;

#ifdef PSX
	VECTOR* spad = (VECTOR*)((u_char*)getScratchAddr(0) + 0x100);
#else
	VECTOR spad[64];
#endif

	VECTOR* playerPos = &spad[0];
	VECTOR* cameraPos = &spad[1];
	VECTOR* vJPos = &spad[2];

	playerPos->vx = pDrawingPed->position.vx;
	playerPos->vy = pDrawingPed->position.vy - 15;	// [A] elevate Tanner model a little bit so his legs are not in the ground (when Z-buffer enabled)
	playerPos->vz = pDrawingPed->position.vz;

	cameraPos->vx = camera_position.vx;
	cameraPos->vy = camera_position.vy;
	cameraPos->vz = camera_position.vz;

	vJPos[ROOT].vx = Skel[ROOT].pvOrigPos->vx;
	vJPos[ROOT].vy = -Skel[ROOT].pvOrigPos->vy;
	vJPos[ROOT].vz = Skel[ROOT].pvOrigPos->vz;

	VECTOR v = { 0, 0, 0 };

	gte_SetTransVector(&v);

	// do not draw the root
	Skel[ROOT].id = (LIMBS)(Skel[ROOT].id | 0x80);

	draw = (pDrawingPed->padId > -1 && pDrawingPed->padId == CurrentPlayerView)
		? player[pDrawingPed->padId].cameraView != 2
		: 1;

	for (i = 0; i < 5; i++)
	{
		for (j = 1; j < 8; j++)
		{
			int id;

			id = lRoutes[i][j];

			if (id == ROOT)
				break;

			BONE* pBone = &Skel[id];

			if (pBone->id < 0x7f)
			{
				int lval;

				lval = lRoutes[i][j - 1];

				vJPos[pBone->id].vx = vJPos[lval].vx + pBone->vCurrPos.vx;
				vJPos[pBone->id].vy = vJPos[lval].vy + pBone->vCurrPos.vy;
				vJPos[pBone->id].vz = vJPos[lval].vz + pBone->vCurrPos.vz;

				if (pDrawingPed->pedType == TANNER_MODEL && pBone->id == HEAD)
					pDrawingPed->head_pos = vJPos[lval].vy;

				if (pBone->pModel != NULL
					&& !bDoingShadow
					&& pDrawingPed->pedType < OTHER_SPRITE
					&& draw)
				{
					MODEL* model = *pBone->pModel;

					for (int c = 0; c < model->num_vertices; c++)
					{
						SVECTOR* mVerts = (SVECTOR*)model->vertices + c;

						mVerts->vx += (vJPos[lval].vx + playerPos->vx - cameraPos->vx);
						mVerts->vy += (vJPos[lval].vy + playerPos->vy - cameraPos->vy);
						mVerts->vz += (vJPos[lval].vz + playerPos->vz - cameraPos->vz);
					}
				}

				pBone->id = (LIMBS)(pBone->id | 0x80);
			}
		}
	}

	if(bDoingShadow || draw)
	{
		if (pDrawingPed->pedType < OTHER_SPRITE)
		{
			// draw LOWERBACK - RTOE
			for (i = 1; i < NUM_BONES - 1; i++)
			{
				int id;
				BONE* pBone;

				pBone = &Skel[i];
				id = pBone->id & 0x7f;

				if (bDoingShadow)
				{
					if (id != LSHOULDER
						&& id != RSHOULDER
						&& id != HIPS
						&& id != LOWERBACK
						&& id != ROOT
						&& id != JOINT
						&& id != LHIP
						&& id != RHIP)
					{
						DrawSprite(pDrawingPed, pBone, vJPos);
					}
				}
				else if (pBone->pModel != NULL)
				{
					MODEL* model = *pBone->pModel;

					int bias = 2;

					if (id == JOINT_1)
						bias = 1;
					else if (id == HEAD)
						bias = 0;
					
					RenderModel(model, NULL, &v, bias, PLOT_NO_SHADE, 0, 0);
				}

#if 0
				// DEBUG DRAW SKELETON
				{
					extern void Debug_AddLine(VECTOR & pointA, VECTOR & pointB, CVECTOR & color);
					extern void Debug_AddLineOfs(VECTOR & pointA, VECTOR & pointB, VECTOR & ofs, CVECTOR & color);

					CVECTOR bbcv = { 0, 0, 250 };
					CVECTOR rrcv = { 250, 0, 0 };
					CVECTOR yycv = { 250, 250, 0 };

					VECTOR v0 = {
						vJPos[pBone->id & 0x7f].vx,
						-vJPos[pBone->id & 0x7f].vy,
						vJPos[pBone->id & 0x7f].vz
					};

					VECTOR v1 = {
						vJPos[pBone->pParent->id & 0x7f].vx,
						-vJPos[pBone->pParent->id & 0x7f].vy,
						vJPos[pBone->pParent->id & 0x7f].vz
					};

					VECTOR ofs = *(VECTOR*)&pDrawingPed->position;
					ofs.vy = -ofs.vy;
					//ofs.vy += 270;

					Debug_AddLineOfs(v0, v1, ofs, bbcv);
				}
#endif
			}
		}
		else
		{
			// draw LOWERBACK - RTOE
			for (i = 1; i < NUM_BONES - 1; i++)
			{
				BONE* pBone = &Skel[i];

				int id = pBone->id & 0x7f;

				if (id != LSHOULDER
					&& id != RSHOULDER
					&& id != HEAD
					&& id != HIPS
					&& id != LOWERBACK
					&& id != ROOT
					&& id != JOINT
					&& id != LHIP
					&& id != RHIP)
				{
					DrawSprite(pDrawingPed, pBone, vJPos);
				}
			}

			// draw HEAD
			BONE* pBone = &Skel[HEAD];

			if (switch_detail_distance >> 2 < gCurrentZ)
			{
				DrawSprite(pDrawingPed, pBone, vJPos);
			}
			else
			{
				SVECTOR v1, v2;

				v1.vx = vJPos[pBone->id & 0x7f].vx;
				v1.vy = vJPos[pBone->id & 0x7f].vy;
				v1.vz = vJPos[pBone->id & 0x7f].vz;

				v2.vx = vJPos[pBone->pParent->id & 0x7f].vx;
				v2.vy = vJPos[pBone->pParent->id & 0x7f].vy;
				v2.vz = vJPos[pBone->pParent->id & 0x7f].vz;

				bAllreadyRotated = 1;
				DoCivHead(pDrawingPed, &v2, &v1);
				bAllreadyRotated = 0;
			}
		}
	}

	// clear all id flags
	for (i = 0; i < NUM_BONES; i++)
		Skel[i].id = (LIMBS)(Skel[i].id & 0x7f);
}

// [D] [T]
SVECTOR* GetModelVertPtr(LPPEDESTRIAN pDrawingPed, int boneId, int modelType)
{
	int startVertex;

	if (pDrawingPed->pedType != OTHER_MODEL)
	{
		if (cTannerVNumbers[boneId & 0x7f] != -1)
			return vTannerList + cTannerVNumbers[boneId & 0x7f];

		return NULL;
	}

	switch (boneId)
	{
		case JOINT_1:
			startVertex = cJerichoVNumbers[0];
			break;
		case HEAD:
			startVertex = cJerichoVNumbers[1];
			break;
		case LELBOW:
			startVertex = cJerichoVNumbers[2];
			break;
		case LHAND:
			startVertex = cJerichoVNumbers[3];
			break;
		case RELBOW:
			startVertex = cJerichoVNumbers[4];
			break;
		case RHAND:
			startVertex = cJerichoVNumbers[5];
			break;
		default:
			return vTannerList + cTannerVNumbers[boneId & 0x7f];
	}

	return vJerichoList + startVertex;
}

// [D] [T]
void newRotateBones(LPPEDESTRIAN pDrawingPed, BONE* poBone)
{
	SVECTOR* pVerts;
	MODEL* pModel;
	MATRIX mStore[32];
	int i, j;

	MATRIX _sMatrix;
	MATRIX _pMatrix;
	MATRIX _oMatrix;

	SVECTOR _svBone[2];
	SVECTOR _pD[80];
	VECTOR _vBoneRotated;

	_pMatrix.t[0] = Skel[0].pvOrigPos->vx;
	_pMatrix.t[1] = Skel[0].pvOrigPos->vy;
	_pMatrix.t[2] = Skel[0].pvOrigPos->vz;

	// [A] it just happened to be bugged and weird implementation of YXZ rotation
	// replaced with regular RotMatrixYXZ
	SVECTOR r;
	r.vx = pDrawingPed->dir.vx & 0xfff;
	r.vy = pDrawingPed->dir.vy & 0xfff;
	r.vz = pDrawingPed->dir.vz & 0xfff;

	RotMatrixYXZ(&r, &_pMatrix);

	_svBone[0].vx = Skel[0].vOffset.vx;
	_svBone[0].vy = Skel[0].vOffset.vy;
	_svBone[0].vz = Skel[0].vOffset.vz;

	mStore[0] = _pMatrix;

	for (i = 0; i < 5; i++)
	{
		for (j = 1; j < 8; j++)
		{
			int id = lRoutes[i][j];

			if (id == ROOT)
				break;

			BONE* pBone = &Skel[id];

			_svBone[0].vx = pBone->vOffset.vx;
			_svBone[0].vy = pBone->vOffset.vy;
			_svBone[0].vz = pBone->vOffset.vz;

			if (bReverseYRotation == 0 || pBone->pParent->id != ROOT)
			{
				_svBone[1].vx = -pBone->pParent->pvRotation->vx;
				_svBone[1].vy = pBone->pParent->pvRotation->vy;
				_svBone[1].vz = pBone->pParent->pvRotation->vz;
			}
			else
			{
				_svBone[1].vx = pBone->pParent->pvRotation->vx;
				_svBone[1].vy = -pBone->pParent->pvRotation->vy;
				_svBone[1].vz = pBone->pParent->pvRotation->vz;
			}

			if (id == HEAD)
				_svBone[1].vy -= pDrawingPed->head_rot;

			RotMatrixZYX_gte(&_svBone[1], &_sMatrix);

			_sMatrix.t[0] = _svBone[0].vx;
			_sMatrix.t[1] = _svBone[0].vy;
			_sMatrix.t[2] = _svBone[0].vz;

			_pMatrix = mStore[pBone->pParent->id & 0x7f];

			gte_MulMatrix0(&_pMatrix, &_sMatrix, &_oMatrix);
			gte_SetRotMatrix(&_oMatrix);

			gte_ldv0(&_svBone[0]);
			gte_rtv0();

			gte_stlvnl(&_vBoneRotated);

			pBone->vCurrPos.vx = _vBoneRotated.vx;
			pBone->vCurrPos.vy = _vBoneRotated.vy;
			pBone->vCurrPos.vz = _vBoneRotated.vz;

			if (pBone->id < 0x7f)
			{
				pVerts = GetModelVertPtr(pDrawingPed, pBone->id, 0);

				if ((pDrawingPed->pedType < OTHER_SPRITE || pBone->id == HEAD)
					&& pBone->pModel != NULL
					&& pVerts != NULL)
				{
					pModel = *pBone->pModel;

					SVECTOR* pmVerts = (SVECTOR*)pModel->vertices;
					int numVerts = pModel->num_vertices;

					for (int c = 0; c < numVerts; c++)
					{
						_pD[c].vx = pVerts[c].vx + _svBone[0].vx;
						_pD[c].vy = pVerts[c].vy + _svBone[0].vy;
						_pD[c].vz = pVerts[c].vz + _svBone[0].vz;
					}

					for (int c = 0; c < numVerts; c++)
					{
						gte_ldv0(&_pD[c]);

						gte_rtv0();

						gte_stlvnl(&_vBoneRotated);

						pmVerts[c].vx = _vBoneRotated.vx;
						pmVerts[c].vy = _vBoneRotated.vy;
						pmVerts[c].vz = _vBoneRotated.vz;
					}
				}
			}

			mStore[id] = _oMatrix;
		}
	};
}


// [D] [T]
void DrawCiv(LPPEDESTRIAN pPed)
{
	SVECTOR* vert2;
	SVECTOR* vert1;
	short size;
	int shift;
	int boneId;
	int frame;
	int i, j;
	int phase;
	SVECTOR pos;
	VECTOR pos1;
	SVECTOR rot;
	MATRIX workmatrix;
	CVECTOR cv;
	VECTOR ppos;
	int bHeadModel;

	DVECTOR sxyList[40];
	int szList[40];
	SVECTOR srLerpData[40];

	bHeadModel = 0;

	frame = pPed->frame1 / 2;

	vert1 = (SVECTOR*)pPed->motion;

	// [A] alpha 1.6 bug fix
	if (!vert1)
		return;

	vert2 = vert1 + frame * 30;

	shift = (pPed->flags >> 15) & 0xFF; // HMMM?

	if (pPed->frame1 & 1)
	{
		if (pPed->frame1 < 30)
			vert1 += (frame + 1) * 30;

		// interpolate between frames
		for (i = 0; i < 31; i++)
		{
			srLerpData[i].vx = vert1->vx + vert2->vx >> 1;
			srLerpData[i].vy = vert1->vy + vert2->vy >> shift + 1;
			srLerpData[i].vz = vert1->vz + vert2->vz >> 1;

			vert1++;
			vert2++;
		}
	}
	else
	{
		for (i = 0; i < 31; i++)
		{
			srLerpData[i].vx = vert2->vx;
			srLerpData[i].vy = vert2->vy >> shift;
			srLerpData[i].vz = vert2->vz;

			vert2++;
		}
	}

	pos.vx = pPed->position.vx - camera_position.vx;
	pos.vy = pPed->position.vy - camera_position.vy;
	pos.vz = pPed->position.vz - camera_position.vz;

	gte_SetRotMatrix(&inv_camera_matrix);
	gte_ldv0(&pos);
	gte_rtv0();

	gte_stlvnl(&pos1);
	gte_SetTransVector(&pos1);

	// [A] it just happened to be bugged and weird implementation of YXZ rotation
	// replaced with regular RotMatrixYXZ
	rot.vx = pPed->dir.vx & 0xfff;
	rot.vy = pPed->dir.vy & 0xfff;
	rot.vz = pPed->dir.vz & 0xfff;

	RotMatrixYXZ(&rot, &workmatrix);
	
	gte_MulMatrix0(&inv_camera_matrix, &workmatrix, &workmatrix);
	gte_SetRotMatrix(&workmatrix);

	gte_ldv3(&srLerpData[0], &srLerpData[1], &srLerpData[2]);
	gte_rtpt();
	gte_stsz(&gCurrentZ);

	szList[0] = gCurrentZ;

	if (gCurrentZ > switch_detail_distance)
		return;

	// translate bones to screen and draw sprites
	j = 0;
	for (i = 0; i < 15; i++)
	{
		if (j < 30)
		{
			gte_stsxy3(&sxyList[j], &sxyList[j+1], &sxyList[j+2]);
			gte_stsz3(&szList[j], &szList[j+1], &szList[j+2]);
		}

		if (j < 27)
		{
			j += 3;

			gte_ldv3(&srLerpData[j], &srLerpData[j+1], &srLerpData[j+2]);
			gte_rtpt();
		}

		boneId = boneIdvals[i];
		
		if (boneId == HEAD && szList[0] <= switch_detail_distance / 2)
			bHeadModel = 1;
		else
			DrawBodySprite(pPed, boneId, (VERTTYPE*)&sxyList[i*2], (VERTTYPE*)&sxyList[i*2 + 1], szList[i*2], szList[i*2+1]);
	}

	// show head
	if (bHeadModel)
	{
		bAllreadyRotated = 0;
		DoCivHead(pPed, &srLerpData[5], &srLerpData[4]);

		ppos.vx = 0;
		ppos.vy = 0;
		ppos.vz = 0;

		gte_SetTransVector(&ppos);
	}

	// make shadow
	ppos.vx = pPed->position.vx;
	ppos.vy = pPed->position.vy;
	ppos.vz = pPed->position.vz;

	phase = (pPed->frame1 & 15) * 2;

	cv.b = 40;
	cv.g = 40;
	cv.r = 40;

	ppos.vy = (10 - MapHeight(&ppos)) - camera_position.vy;
	ppos.vx = ppos.vx - camera_position.vx;
	ppos.vz = ppos.vz - camera_position.vz;

	if (phase < 8)
		size = phase + 80;
	else
		size = -phase + 112;

	RoundShadow(&ppos, &cv, size);
}

// [D] [T]
void SetSkelModelPointers(int type)
{
	if (type == OTHER_MODEL)
	{
		Skel[JOINT_1].pModel = &pmJerichoModels[0];
		Skel[HEAD].pModel = &pmJerichoModels[1];
		Skel[LELBOW].pModel = &pmJerichoModels[2];
		Skel[LHAND].pModel = &pmJerichoModels[3];
		Skel[RELBOW].pModel = &pmJerichoModels[4];
		Skel[RHAND].pModel = &pmJerichoModels[5];
		Skel[LHIP].pModel = NULL;

		bodyShiftValue = BODY_OFFSET + JERICHO_BODY_OFFSET;
		torsoShiftValue = TORSO_OFFSET + JERICHO_TORSO_OFFSET;
	}
	else
	{
		Skel[JOINT_1].pModel = &pmTannerModels[0];
		Skel[HEAD].pModel = &pmTannerModels[1];
		Skel[LELBOW].pModel = &pmTannerModels[8];
		Skel[LHAND].pModel = &pmTannerModels[9];
		Skel[RELBOW].pModel = &pmTannerModels[2];
		Skel[RHAND].pModel = &pmTannerModels[3];
		Skel[LHIP].pModel = &pmTannerModels[15]; // [A] Tanner looks better with no model here but let's keep it

		bodyShiftValue = BODY_OFFSET;
		torsoShiftValue = TORSO_OFFSET;
	}
}


int iCurrBone = 0;

// [D] [T]
void DrawTanner(LPPEDESTRIAN pPed)
{
	int iVar1;
	VECTOR v;
	CVECTOR cV;

	bDoingShadow = 0;

	SetSkelModelPointers(pPed->pedType);
	SetupTannerSkeleton(pPed);

	newRotateBones(pPed, &Skel[LOWERBACK]);

	// [A] I don't know but it works
	gte_SetRotMatrix(&inv_camera_matrix);

	iCurrBone = 0;
	newShowTanner(pPed);

	v.vx = pPed->position.vx;
	v.vy = -pPed->position.vy;
	v.vz = pPed->position.vz;
	
	v.vy = -camera_position.vy - MapHeight(&v);// - camera_position.vy;

	v.vx = (pPed->position.vx - camera_position.vx) + Skel[ROOT].pvOrigPos->vx;
	v.vz = (pPed->position.vz - camera_position.vz) + Skel[ROOT].pvOrigPos->vz;
	//v.vy = -camera_position.vy - MapHeight((VECTOR*)&pPed->position);

	bDoingShadow = 1;

	if (pPed->padId == 0)
	{
		if (gTimeOfDay == 3)
		{
			cV.b = 12;
			cV.g = 12;
			cV.r = 12;
			TannerShadow(pPed, &v, moon_position + GameLevel, &cV, pPed->dir.vy);
		}
		else
		{
			cV.b = 32;
			cV.g = 32;
			cV.r = 32;
			TannerShadow(pPed, &v, sun_position + GameLevel, &cV, pPed->dir.vy);
		}
	}
	bDoingShadow = 0;
}

// [D] [T]
int DrawCharacter(LPPEDESTRIAN pPed)
{
	int fr;
	short size;
	int phase;

	CVECTOR cV;
	VECTOR v;
	CVECTOR cv;
	VECTOR pos;

	SetSkelModelPointers(pPed->pedType);
	SetupTannerSkeleton(pPed);

	newRotateBones(pPed, &Skel[LOWERBACK]);

	gte_SetRotMatrix(&inv_camera_matrix);

	iCurrBone = 0;
	newShowTanner(pPed);

	if (pUsedPeds->pNext == NULL && pPed->pedType == TANNER_MODEL)
	{
		v.vx = (pPed->position.vx - camera_position.vx) + Skel[ROOT].pvOrigPos->vx;
		v.vz = (pPed->position.vz - camera_position.vz) + Skel[ROOT].pvOrigPos->vz;

		bDoingShadow = 1;
		v.vy = -camera_position.vy - MapHeight((VECTOR*)&pPed->position);

		if (gTimeOfDay == 3)
		{
			cV.b = cV.g = cV.r = 12;
			TannerShadow(pPed, &v, moon_position + GameLevel, &cV, pPed->dir.vy);
		}
		else
		{
			cV.b = cV.g = cV.r = 32;
			TannerShadow(pPed, &v, sun_position + GameLevel, &cV, pPed->dir.vy);
		}

		bDoingShadow = 0;
	}
	else if (pPed->pedType == CIVILIAN)
	{
		pos.vx = pPed->position.vx;
		pos.vy = pPed->position.vy;
		pos.vz = pPed->position.vz;

		fr = pPed->frame1 & 7;
		phase = fr * 2;

		pos.vy = (30 - MapHeight(&pos)) - camera_position.vy;

		pos.vx -= camera_position.vx;
		pos.vz -= camera_position.vz;

		if (phase < 8)
			size = phase + 80;
		else
			size = -phase + 96;

		cv.b = cv.g = cv.r = 40;
		
		RoundShadow(&pos, &cv, size);
	}

	return 1;
}


POLY_FT4 ft4TannerShadow[2];
TILE tileTannerClear[2];
extern TEXTURE_DETAILS tannerShadow_texture;
RECT16 rectTannerWindow;

// [D] [T]
void InitTannerShadow(void)
{
	u_char brightness;
	TILE* tile;
	POLY_FT4* poly;
	int i;

	if (gTimeOfDay == 3)
		brightness = 12;
	else
		brightness = 32;

	poly = ft4TannerShadow;
	tile = tileTannerClear;
	
	rectTannerWindow.w = 64;
	rectTannerWindow.h = 128;
	rectTannerWindow.x = tpagepos[nperms + 1].x;
	rectTannerWindow.y = tpagepos[nperms + 1].y + 128;

	for (i = 0; i < 2; i++)
	{
		int offs = 0;
		if(GameLevel == 2) // [A] bug fix for vegas
			offs = 18;
		
		poly->u0 = tannerShadow_texture.coords.u1 / 4 - 1;
		poly->v0 = tannerShadow_texture.coords.v1 + offs;
		
		poly->u1 = tannerShadow_texture.coords.u0 / 4 + 1;
		poly->v1 = tannerShadow_texture.coords.v0 + offs;
		
		poly->u2 = tannerShadow_texture.coords.u3 / 4 - 1;
		poly->v2 = tannerShadow_texture.coords.v3 + offs - 18;

		poly->u3 = tannerShadow_texture.coords.u2 / 4 + 1;
		poly->v3 = tannerShadow_texture.coords.v2 + offs - 18;

		poly->tpage = getTPage(2, 0, rectTannerWindow.x, rectTannerWindow.y);

		setPolyFT4(poly);
		setSemiTrans(poly, 1);

		poly->r0 = poly->g0 = poly->b0 = brightness;
		poly++;

		setTile(tile);

		tile->x0 = 0;
		tile->y0 = 0;
		tile->w = 64;
		tile->h = 128;
		tile->r0 = 0;
		tile->g0 = 0;
		tile->b0 = 0;
		tile++;
	}
}

// [D] [T]
void TannerShadow(LPPEDESTRIAN pDrawingPed, VECTOR* pPedPos, SVECTOR* pLightPos, CVECTOR* col, short angle)
{
	DR_ENV* dr_env;
	SVECTOR vert[4];
	VECTOR d;
	DRAWENV drEnv;

	VECTOR cp;
	SVECTOR ca;

	VECTOR myVector;
	int z0, z1, z2, z3;
	int i;
	int cn, sn;
	int vx, vz;

	int Tangle;

#ifndef PSX
	if (gDemoLevel)
		return;
#endif

#ifdef __EMSCRIPTEN__
	// since any WebGL isn't able to make it drawn...
	return;
#endif

	if (NumPlayers > 1)
		return;

	memset((u_char*)&d, 0, sizeof(VECTOR));

	SetDefDrawEnv(&drEnv, 0, current->draw.clip.y, 320, 256);

	dr_env = (DR_ENV*)current->primptr;
	SetDrawEnv(dr_env, &drEnv);

	addPrim(current->ot + OTSIZE - 1, dr_env);
	current->primptr += sizeof(DR_ENV);

	Tangle = ratan2(-pLightPos->vx, pLightPos->vz);

	SetVec(&vert[0], -128, 0, -320);
	SetVec(&vert[1], 128, 0, -320);
	SetVec(&vert[2], -128, 0, 40);
	SetVec(&vert[3], 128, 0, 40);

	for (i = 0; i < 4; i++)
	{
		cn = rcos(Tangle); sn = rsin(Tangle);

		vx = vert[i].vx; vz = vert[i].vz;

		vert[i].vx = FIXED(vx * cn) - FIXED(vz * sn);
		vert[i].vz = FIXED(vx * sn) + FIXED(vz * cn);

		VecAdd(&vert[i], &vert[i], pPedPos);
	}

	gte_SetRotMatrix(&inv_camera_matrix);
	gte_SetTransVector(&d);

	gte_ldv3(&vert[0], &vert[1], &vert[2]);
	gte_rtpt();

	gte_stsxy3(&ft4TannerShadow[current->id].x0, &ft4TannerShadow[current->id].x1, &ft4TannerShadow[current->id].x2);
	gte_stsz3(&z0, &z1, &z2);

	gte_ldv0(&vert[3]);

	gte_rtps();

	gte_stsxy(&ft4TannerShadow[current->id].x3);
	gte_stsz(&z3);

	if (z0 < z1)
		z0 = (z0 + z1) / 2;
	else
		z1 = (z0 + z1) / 2;

	if (z2 < z3)
		z2 = (z2 + z3) / 2;
	else
		z3 = (z2 + z3) / 2;

	if (z0 > 28)
		z0 = z0 - 20;
	else
		z0 = 8;

	if (z1 > 28)
		z1 = z1 - 20;
	else
		z1 = 8;

	if (z2 > 28)
		z2 = z2 - 20;
	else
		z2 = 8;

	if (z3 > 28)
		z3 = z3 - 20;
	else
		z3 = 8;

	addPrim(current->ot + (z0 * 2 + z3 * 6 >> 6), &ft4TannerShadow[current->id]);
	//SubdivShadow(z0, z1, z2, z3, ft4TannerShadow + current->id);
	
	{
		// store vectors
		cp = camera_position;
		ca = camera_angle;
		z1 = scr_z;

		// setup new camera
		SetVec(&camera_position,
			pDrawingPed->position.vx + (pLightPos->vx * 90 >> 0xc),
			pDrawingPed->position.vy + (pLightPos->vy * 90 >> 0xc),
			pDrawingPed->position.vz + (pLightPos->vz * 90 >> 0xc));

		SetVec(&myVector, pDrawingPed->position.vx, pDrawingPed->position.vy, pDrawingPed->position.vz);
		PointAtTarget(&camera_position, &myVector, &camera_angle);

		// build matrix
		gte_SetGeomOffset(32, 60);
		SetGeomScreen(scr_z = 200);
		BuildWorldMatrix();

		// clear to black and draw Tanner sprites
		newShowTanner(pDrawingPed);
		addPrim(current->ot + OTSIZE - 1, &tileTannerClear[current->id]);

		// restore vectors
		camera_position = cp;
		camera_angle = ca;
		scr_z = z1;

		// build matix
		gte_SetGeomOffset(160, 128);
		SetGeomScreen(scr_z);
		BuildWorldMatrix();
	}
	
	SetDefDrawEnv(&drEnv, rectTannerWindow.x, rectTannerWindow.y, rectTannerWindow.w, rectTannerWindow.h);
	drEnv.dfe = 0; // we're drawing into VRAM - don't draw on screen
	drEnv.dtd = 0; // [A] no need in dithering

	dr_env = (DR_ENV*)current->primptr;
	SetDrawEnv(dr_env, &drEnv);

	addPrim(current->ot + OTSIZE - 1, dr_env);
	current->primptr += sizeof(DR_ENV);
}

// [A] - totally custom function but it works pretty much same as original
void DoCivHead(LPPEDESTRIAN pPed, SVECTOR* vert1, SVECTOR* vert2)
{
	SVECTOR spos;
	VECTOR pos;
	SVECTOR headpos;
	MATRIX* pHeadRot;
	int oldcombointensity;

	if (gPed1HeadModelPtr == NULL)
		return;

	pHeadRot = (MATRIX*)&matrixtable[((pPed->dir.vy - pPed->head_rot) / 64) & 0x3F];

	if (bAllreadyRotated) // not needed to rotate vert1
	{
		headpos.vx = vert1->vx;
		headpos.vy = vert1->vy;
		headpos.vz = vert1->vz;
	}
	else
	{
		gte_SetRotMatrix(&matrixtable[((pPed->dir.vy) / 64) & 0x3F]);

		gte_ldv0(vert1);
		gte_rtv0();

		gte_stsv(&headpos);
	}

	spos.vx = headpos.vx + pPed->position.vx - camera_position.vx;
	spos.vy = headpos.vy + pPed->position.vy - camera_position.vy;
	spos.vz = headpos.vz + pPed->position.vz - camera_position.vz;

	gte_SetRotMatrix(&inv_camera_matrix);
	gte_ldv0(&spos);
	gte_rtv0();

	gte_stlvnl(&pos);

	gte_SetTransVector(&pos);

	int flags = PLOT_NO_SHADE;

	if (pPed->pallet & 0xf)
	{
		flags |= PLOT_CUSTOM_PALETTE; // set custom palette flag
		plotContext.clut = civ_clut[0][texturePedHead.texture_number][pPed->pallet & 0xf] << 0x10;
	}

	oldcombointensity = combointensity;

	if (gNight)
		combointensity = 0x404040;

	RenderModel(gPed1HeadModelPtr, pHeadRot, &pos, 1, flags, 0, 0);

	combointensity = oldcombointensity;
}


