// TODO: stay at least 8 units away from all walls in this leaf
void Chase_UpdateForDrawing (void)
{
	int		i;
	vec3_t	forward, up, right;
	vec3_t	ideal, crosshair, temp;

	Math_AngleVectors(cl.viewangles, forward, right, up);

	// calc ideal camera location before checking for walls
	for (i=0 ; i<3 ; i++)
		ideal[i] = cl.viewent.origin[i]
		- forward[i]*chase_back.value
		+ right[i]*chase_right.value;
		//+ up[i]*chase_up.value;
	ideal[2] = cl.viewent.origin[2] + chase_up.value;

	// make sure camera is not in or behind a wall
	TraceLine(r_refdef.vieworg, ideal, temp);
	if(Math_Length(temp) != 0)
		Math_VectorCopy(temp, ideal);

	// place camera
	Math_VectorCopy(ideal, r_refdef.vieworg);

	// find the spot the player is looking at
	Math_VectorMA(cl.viewent.origin, 4096, forward, temp);
	TraceLine(cl.viewent.origin, temp, crosshair);

	// calculate camera angles to look at the same spot
	Math_VectorSubtract(crosshair,r_refdef.vieworg,temp);
	Math_VectorAngles (temp, r_refdef.viewangles);
	if (r_refdef.viewangles[PITCH] == 90 || r_refdef.viewangles[PITCH] == -90)
		r_refdef.viewangles[YAW] = cl.viewangles[YAW];
}
void TridentHit(ServerEntity_t *ent)
{
	vec3_t	forward,temp,sndvec,vel;
	trace_t	trace;

	Math_AngleVectors(ent->v.v_angle,forward,temp,temp);

	// [18/4/2012] A nice soft bounce ~hogsy
	vel[0] = vel[1] = 0;
	vel[2] = 0.5;

	sndvec[0] = ent->v.origin[0]+forward[0]*78;
	sndvec[1] = ent->v.origin[1]+forward[1]*78;
	sndvec[2] = ent->v.origin[2]+forward[2]*78;

	trace = Traceline(ent,ent->v.origin,sndvec,0);

	sndvec[0] = trace.endpos[0]-forward[0]*4;
	sndvec[1] = trace.endpos[1]-forward[1]*4;
	sndvec[2] = trace.endpos[2]-forward[2]*4;

	if(trace.fraction == 1.0f)
		return;
	if(trace.ent->v.bTakeDamage)
    {
		if(trace.ent->local.bBleed)
			Engine.Particle(sndvec,vel,10,"blood",30);

		Entity_Damage(trace.ent,ent,20,0);
	}
	else if(trace.ent)
		Engine.Particle(sndvec,vel,10,"smoke",30);
}
void Area_SetMoveDirection(PLVector3D &angles, PLVector3D &direction) {
	if(angles == PLVector3D(0, -1, 0)) {
        direction = PLVector3D(0, 0, 1);
	} else if(angles == PLVector3D(0, -2, 0)) {
        direction = PLVector3D(0, 0, -1);
    } else {
        Math_AngleVectors(angles, &direction, nullptr, nullptr);
    }

    plClearVector3D(&angles);
}
// [4/8/2012] Renamed to SideWinder_SpawnMissle ~hogsy
void SideWinder_SpawnMissle(edict_t *ent,float fSpeed,float ox)
{
	// [26/2/2012] Revised and fixed ~hogsy
	vec3_t	vOrg;
	edict_t *eMissile = Entity_Spawn();

	/*	TODO:
			Spawn a flare at our position too
		~hogsy
	*/

	eMissile->v.cClassname	= "sidewindermissile";
	eMissile->v.movetype	= MOVETYPE_FLYMISSILE;
	eMissile->v.effects		= EF_PARTICLE_SMOKE|EF_DIMLIGHT;

	eMissile->Physics.iSolid	= SOLID_BBOX;
	eMissile->Physics.eIgnore	= ent;

	eMissile->local.speed	= SIDEWINDER_MAXSPEED;
	eMissile->local.eOwner	= ent;
	eMissile->local.count	= 0;
	// [4/8/2012] Change our speed depending on what contents we're within ~hogsy
	eMissile->local.speed	= fSpeed;

	Math_VectorScale(Engine.Aim(ent),eMissile->local.speed,eMissile->v.velocity);

	Math_AngleVectors(ent->v.v_angle,
		// [4/8/2012] Set up our angle vectors ~hogsy
		eMissile->v.vForward,
		eMissile->local.vRight,
		eMissile->local.vUp);
	Math_VectorCopy(ent->v.v_angle,eMissile->v.angles);

	Entity_SetModel(eMissile,"models/sidewinder_missile.md2");

	Math_VectorCopy(ent->v.origin,vOrg);

	vOrg[0] += eMissile->v.vForward[0]*8+eMissile->local.vRight[0]*ox;
	vOrg[1] += eMissile->v.vForward[1]*8+eMissile->local.vRight[1]*ox;
	vOrg[2] += eMissile->v.vForward[2]*24;

	Entity_SetSizeVector(eMissile,mv3Origin,mv3Origin);
	Entity_SetOrigin(eMissile,vOrg);

	// [4/8/2012] Time at which we'll be removed if nothing hit ~hogsy
	eMissile->local.fSpawnDelay = (float)(Server.dTime+8.0);

	eMissile->v.TouchFunction	= SideWinder_MissileExplode;
	eMissile->v.dNextThink		= Server.dTime+0.05;
	eMissile->v.think			= SideWinder_Think;

	// [4/8/2012] Moved so we do this last! ~hogsy
	Engine.LinkEntity(eMissile,false);
}
void R_SetupView (void)
{
	Fog_SetupFrame (); //johnfitz

// build the transformation matrix for the given view angles
	Math_VectorCopy(r_refdef.vieworg,r_origin);
	Math_AngleVectors(r_refdef.viewangles,vpn,vright,vup);

	// Current viewleaf
	r_oldviewleaf	= r_viewleaf;
	r_viewleaf		= Mod_PointInLeaf (r_origin, cl.worldmodel);

	V_SetContentsColor(r_viewleaf->contents);

	View_CalculateBlend();

	//johnfitz -- calculate r_fovx and r_fovy here
	r_fovx = r_refdef.fov_x;
	r_fovy = r_refdef.fov_y;
	if (r_waterwarp.value)
	{
		int contents = Mod_PointInLeaf (r_origin, cl.worldmodel)->contents;
		if (contents == BSP_CONTENTS_WATER || contents == BSP_CONTENTS_SLIME || contents == BSP_CONTENTS_LAVA)
		{
			//variance is a percentage of width, where width = 2 * tan(fov / 2) otherwise the effect is too dramatic at high FOV and too subtle at low FOV.  what a mess!
			r_fovx = atan(tan(pMath_DEG2RAD(r_refdef.fov_x) / 2) * (0.97 + sin(cl.time * 1.5) * 0.03)) * 2 / pMath_PI_DIV180;
			r_fovy = atan(tan(pMath_DEG2RAD(r_refdef.fov_y) / 2) * (1.03 - sin(cl.time * 1.5) * 0.03)) * 2 / pMath_PI_DIV180;
		}
	}
	//johnfitz

	R_SetFrustum(r_fovx, r_fovy); //johnfitz -- use r_fov* vars
	R_MarkSurfaces(); //johnfitz -- create texture chains from PVS
	R_CullSurfaces(); //johnfitz -- do after R_SetFrustum and R_MarkSurfaces
	R_UpdateWarpTextures(); //johnfitz -- do this before R_Clear

	//johnfitz -- cheat-protect some draw modes
	r_drawflat_cheatsafe = r_fullbright_cheatsafe = r_lightmap_cheatsafe = false;
	r_drawworld_cheatsafe = true;
	if (cl.maxclients == 1)
	{
		if (!r_drawworld.value)
			r_drawworld_cheatsafe = false;

		if (r_drawflat.value)
			r_drawflat_cheatsafe = true;
		else if (r_fullbright.value || !cl.worldmodel->lightdata)
			r_fullbright_cheatsafe = true;
		else if (r_lightmap.value)
			r_lightmap_cheatsafe = true;
	}
	//johnfitz
}
void R_SetupGenericView(void)
{
	Fog_SetupFrame();

	// Build the transformation matrix for the given view angles
	Math_VectorCopy(r_refdef.vieworg, r_origin);
	Math_AngleVectors(r_refdef.viewangles, vpn, vright, vup);

	r_fovx = r_refdef.fov_x;
	r_fovy = r_refdef.fov_y;

	R_SetFrustum(r_fovx, r_fovy);

	Video_ClearBuffer();
}
void SV_WallFriction (edict_t *ent, trace_t *trace)
{
	vec3_t		forward, right, up;
	float		d, i;
	vec3_t		into, side;

	Math_AngleVectors(ent->v.v_angle, forward, right, up);
	d = Math_DotProduct (trace->plane.normal, forward);

	d += 0.5;
	if (d >= 0)
		return;

	// Cut the tangential velocity
	i = Math_DotProduct (trace->plane.normal, ent->v.velocity);
	Math_VectorScale (trace->plane.normal, i, into);
	Math_VectorSubtract (ent->v.velocity, into, side);

	ent->v.velocity[0] = side[0] * (1 + d);
	ent->v.velocity[1] = side[1] * (1 + d);
}
void Physics_WallFriction(ServerEntity_t *eEntity, trace_t *trLine)
{
	MathVector3f_t forward, right, up;
	float d, i;
	MathVector3f_t into, side;

	Math_AngleVectors(eEntity->v.v_angle, forward, right, up);
	d = Math_DotProduct(trLine->plane.normal, forward);

	d += 0.5;
	if (d >= 0)
		return;

	// Cut the tangential velocity.
	i = Math_DotProduct(trLine->plane.normal, eEntity->v.velocity);
	Math_VectorScale(trLine->plane.normal, i, into);
	Math_VectorSubtract(eEntity->v.velocity, into, side);

	eEntity->v.velocity[0] = side[0] * (1 + d);
	eEntity->v.velocity[1] = side[1] * (1 + d);
}
// [18/5/2013] TODO: Merge with SV_PushMove ~hogsy
static void Server_PushRotate(edict_t *pusher,float movetime)
{
	int		i,e,num_moved,slaves_moved;
	edict_t	*check,*block,*moved_edict[MAX_EDICTS],*ground,*slave,*master;
	vec3_t	move,a,amove,entorig,pushorig,moved_from[MAX_EDICTS],org,org2,forward,right,up;
	bool	bMoveIt;

	for (i = 0; i < 3; i++)
		amove[i] = pusher->v.avelocity[i] * movetime;

	Math_VectorNegate(amove,a);
	Math_AngleVectors(a,forward,right,up);

	Math_VectorCopy(pusher->v.angles,pushorig);

	// move the pusher to it's final position
	Math_VectorAdd(pusher->v.angles,amove,pusher->v.angles);

	pusher->v.ltime += movetime;
	SV_LinkEdict (pusher, false);

	slaves_moved = 0;
	master = pusher;
	while(master->v.aiment)
	{
		slave = PROG_TO_EDICT(master->v.aiment);

		slaves_moved++;
		Math_VectorCopy (slave->v.angles, moved_from[MAX_EDICTS - slaves_moved]);
		moved_edict[MAX_EDICTS - slaves_moved] = slave;

		if (slave->v.movedir[PITCH])
			slave->v.angles[PITCH] = master->v.angles[PITCH];
		else
			slave->v.angles[PITCH] += slave->v.avelocity[PITCH] * movetime;

		if (slave->v.movedir[YAW])
			slave->v.angles[YAW] = master->v.angles[YAW];
		else
			slave->v.angles[YAW] += slave->v.avelocity[YAW] * movetime;

		if (slave->v.movedir[ROLL])
			slave->v.angles[ROLL] = master->v.angles[ROLL];
		else
			slave->v.angles[ROLL] += slave->v.avelocity[ROLL] * movetime;

		slave->v.ltime = master->v.ltime;
		SV_LinkEdict(slave,false);

		master = slave;
	}

	// see if any solid entities are inside the final position
	num_moved = 0;
	check = NEXT_EDICT(sv.edicts);
	for (e = 1; e < sv.num_edicts; e++, check = NEXT_EDICT(check))
	{
		if (check->free)
			continue;
		if (check->v.movetype == MOVETYPE_PUSH || check->v.movetype == MOVETYPE_NONE || check->v.movetype == MOVETYPE_NOCLIP)
			continue;

		// if the entity is standing on the pusher, it will definitely be moved
		bMoveIt = false;
		ground = check->v.groundentity;
		if(check->v.flags & FL_ONGROUND)
		{
			if (ground == pusher)
				bMoveIt = true;
			else
			{
				for (i = 0; i < slaves_moved; i++)
				{
					if (ground == moved_edict[MAX_EDICTS - i - 1])
					{
						bMoveIt = true;
						break;
					}
				}
			}
		}

		if(!bMoveIt)
		{
			if(	check->v.absmin[0] >= pusher->v.absmax[0]
			||	check->v.absmin[1] >= pusher->v.absmax[1]
			||	check->v.absmin[2] >= pusher->v.absmax[2]
			||	check->v.absmax[0] <= pusher->v.absmin[0]
			||	check->v.absmax[1] <= pusher->v.absmin[1]
			||	check->v.absmax[2] <= pusher->v.absmin[2])
			{
				for (i = 0; i < slaves_moved; i++)
				{
					slave = moved_edict[MAX_EDICTS-i-1];
					if( check->v.absmin[0] >= slave->v.absmax[0]
					||	check->v.absmin[1] >= slave->v.absmax[1]
					||	check->v.absmin[2] >= slave->v.absmax[2]
					||	check->v.absmax[0] <= slave->v.absmin[0]
					||	check->v.absmax[1] <= slave->v.absmin[1]
					||	check->v.absmax[2] <= slave->v.absmin[2] )
						continue;
				}
				if (i == slaves_moved)
					continue;
			}

			// See if the ent's bbox is inside the pusher's final position
			if(!SV_TestEntityPosition(check))
				continue;
		}

		// remove the onground flag for non-players
		if(check->v.movetype != MOVETYPE_WALK)
			check->v.flags = check->v.flags & ~FL_ONGROUND;

		Math_VectorCopy(check->v.origin,entorig);
		Math_VectorCopy(check->v.origin,moved_from[num_moved]);
		moved_edict[num_moved] = check;
		num_moved++;

		// calculate destination position
		Math_VectorSubtract(check->v.origin,pusher->v.origin,org);
		org2[0] = Math_DotProduct(org,forward);
		org2[1] = -Math_DotProduct(org,right);
		org2[2] = Math_DotProduct(org,up);
		Math_VectorSubtract (org2,org,move);

		// try moving the contacted entity
		pusher->Physics.iSolid = SOLID_NOT;
		SV_PushEntity (check, move);
	//@@TODO: do we ever want to do anybody's angles?  maybe just yaw???
		if(check->monster.iType != 1)
		{
#if 0
			vec3_t	vYaw;
			
			vYaw[YAW] = Math_AngleMod(pusher->v.angles[YAW]+check->v.angles[YAW]);

			Con_Printf("%i %i\n",(int)check->v.angles[YAW],(int)vYaw[YAW]);
			//check->v.angles[YAW] = vYaw[YAW];
			//check->v.angles[YAW] = Math_AngleMod(vYaw[YAW]);
#endif
			// move back any entities we already moved
			for (i = 0; i < num_moved; i++)
			{
				Math_VectorAdd(moved_edict[i]->v.angles, amove, moved_edict[i]->v.angles);
				moved_edict[i]->v.angles[YAW] += amove[YAW]/2.0f;

				SV_LinkEdict(moved_edict[i],false);
			}
		}

		pusher->Physics.iSolid = SOLID_BSP;

		// If it is still inside the pusher, block
		block = SV_TestEntityPosition (check);
		if (block)
		{
			// fail the move
			if (check->v.mins[0] == check->v.maxs[0])
				continue;
			if (check->Physics.iSolid == SOLID_NOT || check->Physics.iSolid == SOLID_TRIGGER)
			{
				// corpse
				check->v.mins[0] = check->v.mins[1] = 0;
				Math_VectorCopy(check->v.mins,check->v.maxs);
				continue;
			}

			Math_VectorCopy(entorig,check->v.origin);
			SV_LinkEdict(check,true);

			Math_VectorCopy(pushorig,pusher->v.angles);
			SV_LinkEdict(pusher,false);
			pusher->v.ltime -= movetime;

			for(i = 0; i < slaves_moved; i++)
			{
				slave = moved_edict[MAX_EDICTS - i - 1];
				Math_VectorCopy(moved_from[MAX_EDICTS - i - 1], slave->v.angles);
				SV_LinkEdict(slave,false);
				slave->v.ltime -= movetime;
			}

			// if the pusher has a "blocked" function, call it
			// otherwise, just stay in place until the obstacle is gone
			if (pusher->v.blocked)
				pusher->v.blocked(pusher,check);

			// move back any entities we already moved
			for (i = 0; i < num_moved; i++)
			{
				Math_VectorCopy (moved_from[i], moved_edict[i]->v.origin);
			//@@TODO:: see above
			//	if (!((int)moved_edict[i]->v.flags & (FL_CLIENT | FL_MONSTER)))
				Math_VectorSubtract (moved_edict[i]->v.angles, amove, moved_edict[i]->v.angles);
				moved_edict[i]->v.angles[YAW] -= amove[YAW];

				SV_LinkEdict(moved_edict[i],false);
			}
			return;
		}
	}
}
void R_RenderView (void)
{
	double	time1 = 0,
			time2;

	if (r_norefresh.value)
		return;

	if (!cl.worldmodel)
		Sys_Error ("R_RenderView: NULL worldmodel");

	if(r_speeds.value)
	{
		glFinish ();
		time1 = System_DoubleTime();

		//johnfitz -- rendering statistics
		rs_brushpolys = rs_aliaspolys = rs_skypolys = rs_particles = rs_fogpolys = rs_megatexels =
		rs_dynamiclightmaps = rs_aliaspasses = rs_skypasses = rs_brushpasses = 0;
	}

	R_SetupView (); //johnfitz -- this does everything that should be done once per frame

	Video_ClearBuffer();

	//johnfitz -- stereo rendering -- full of hacky goodness
	if (r_stereo.value)
	{
		float	eyesep = Math_Clamp(-8.0f, r_stereo.value, 8.0f),
			fdepth = Math_Clamp(32.0f, r_stereodepth.value, 1024.0f);

		Math_AngleVectors(r_refdef.viewangles, vpn, vright, vup);

		// Render left eye (red)
		glColorMask(true,false,false,true);
		Math_VectorMA (r_refdef.vieworg, -0.5f * eyesep, vright, r_refdef.vieworg);
		frustum_skew = 0.5f*eyesep*NEARCLIP/fdepth;
		srand((int) (cl.time * 1000)); //sync random stuff between eyes

		R_RenderScene();

		// Render right eye (cyan)
		glClear (GL_DEPTH_BUFFER_BIT);
		glColorMask(0, 1, 1, 1);
		Math_VectorMA (r_refdef.vieworg, 1.0f * eyesep, vright, r_refdef.vieworg);
		frustum_skew = -frustum_skew;
		srand((int) (cl.time * 1000)); //sync random stuff between eyes

		R_RenderScene();

		// Restore
		glColorMask(true,true,true,true);
		Math_VectorMA (r_refdef.vieworg, -0.5f * eyesep, vright, r_refdef.vieworg);
		frustum_skew = 0.0f;
	}
	else
		R_RenderScene();
	//johnfitz

	//johnfitz -- modified r_speeds output
	time2 = System_DoubleTime();
	if(r_speeds.value == 2)
		Con_Printf(	"%3i ms  %4i/%4i wpoly %4i/%4i epoly %3i lmap %4i/%4i sky %1.1f mtex\n",
					(int)((time2-time1)*1000),
					rs_brushpolys,
					rs_brushpasses,
					rs_aliaspolys,
					rs_aliaspasses,
					rs_dynamiclightmaps,
					rs_skypolys,
					rs_skypasses,
					TexMgr_FrameUsage ());
	else if(r_speeds.value)
		Con_Printf ("%3i ms  %4i wpoly %4i epoly %3i lmap\n",
					(int)((time2-time1)*1000),
					rs_brushpolys,
					rs_aliaspolys,
					rs_dynamiclightmaps);
	//johnfitz
}
void Sky_ProcessEntities(void)
{
	int				i,k,mark;
	unsigned int	j;
	float			dot;
	bool			bRotated;
	ClientEntity_t	*e;
	msurface_t		*s;
	glpoly_t		*p;
	MathVector3f_t	vTemp, forward, right, up;

	if (!r_drawentities.value)
		return;

	for (i=0 ; i<cl_numvisedicts ; i++)
	{
		e = cl_visedicts[i];

		if (e->model->type != MODEL_TYPE_LEVEL)
			continue;

		if(R_CullModelForEntity(e))
			continue;

		if(e->alpha == ENTALPHA_ZERO)
			continue;

		Math_VectorSubtract (r_refdef.vieworg, e->origin, modelorg);
		if(e->angles[0] || e->angles[1] || e->angles[2])
		{
			bRotated = true;

			Math_AngleVectors(e->angles, forward, right, up);
			Math_VectorCopy(modelorg,vTemp);

			modelorg[0] = Math_DotProduct(vTemp,forward);
			modelorg[1] = -Math_DotProduct(vTemp,right);
			modelorg[2] = Math_DotProduct(vTemp,up);
		}
		else
			bRotated = false;

		s = &e->model->surfaces[e->model->firstmodelsurface];

		for (j=0 ; j<e->model->nummodelsurfaces ; j++, s++)
		{
			if (s->flags & SURF_DRAWSKY)
			{
				dot = Math_DotProduct (modelorg, s->plane->normal) - s->plane->dist;
				if (((s->flags & SURF_PLANEBACK) && (dot < -BACKFACE_EPSILON)) ||
					(!(s->flags & SURF_PLANEBACK) && (dot > BACKFACE_EPSILON)))
				{
					//copy the polygon and translate manually, since Sky_ProcessPoly needs it to be in world space
					mark = Hunk_LowMark();
					p = (glpoly_t*)Hunk_Alloc (sizeof(*s->polys)); //FIXME: don't allocate for each poly
					p->numverts = s->polys->numverts;
					for (k=0; k<p->numverts; k++)
					{
						if(bRotated)
						{
							p->verts[k][0] = e->origin[0] + s->polys->verts[k][0] * forward[0]
														  - s->polys->verts[k][1] * right[0]
														  + s->polys->verts[k][2] * up[0];
							p->verts[k][1] = e->origin[1] + s->polys->verts[k][0] * forward[1]
														  - s->polys->verts[k][1] * right[1]
														  + s->polys->verts[k][2] * up[1];
							p->verts[k][2] = e->origin[2] + s->polys->verts[k][0] * forward[2]
														  - s->polys->verts[k][1] * right[2]
														  + s->polys->verts[k][2] * up[2];
						}
						else
							Math_VectorAdd(s->polys->verts[k], e->origin, p->verts[k]);
					}
					Sky_ProcessPoly (p);
					Hunk_FreeToLowMark (mark);
				}
			}
		}
	}
}
void Entity_MakeVectors(ServerEntity_t *eEntity)
{
	Math_AngleVectors(eEntity->v.v_angle, eEntity->local.vForward, eEntity->local.vRight, eEntity->local.vUp);
}
void Area_ClimbTouch(ServerEntity_t *area, ServerEntity_t *other) {
	PLVector3D ladder_velocity, vPlayerVec;

	if ((other->local.ladderjump > Server.time) || (other->v.waterlevel > 1) || (other->v.flags & FL_WATERJUMP)) {
        return;
    }

    PLVector3D forward, right, up;
    Math_AngleVectors(other->v.angles, &forward, &right, &up);

    PLVector3D playervec = forward;
    playervec *= 250;

	if (other->v.button[2]) {
        playervec = other->v.velocity;
    }

	// ignore 8 units of the top edge
	if (other->v.origin.z + other->v.mins.z + 8 >= area->v.absmax.z) {
		if (!(other->v.flags & FL_ONGROUND)) {
            other->v.flags = other->v.flags + FL_ONGROUND;
        }
		return;
	}

	// null out gravity in PreThink
	other->local.laddertime = Server.time + 0.1;
	other->local.zerogtime = Server.time + 0.1;
	other->v.velocity.z = 0;

	if (other->v.velocity.DotProduct(right) > 25) {
        other->v.velocity = 0;

		other->v.origin.x += right.x * 0.5f;
		other->v.origin.y += right.y * 0.5f;
		other->v.origin.z += right.z * 0.5f;
	//	printf("right  ");
		return;
	} else if (other->v.velocity.DotProduct(right) < -25) {
        other->v.velocity = 0;

		other->v.origin.x -= right.x * 0.5f;
		other->v.origin.y -= right.y * 0.5f;
		other->v.origin.z -= right.z * 0.5f;
	//	printf("left  ");
		return;
	}

	float forwardspeed = forward.DotProduct(other->v.velocity);
	ladder_velocity = 0;

	if ((other->v.v_angle.x <= 15) && (forwardspeed > 0)) { // up (facing up/forward)
		//other->v.origin[0] -= area->v.movedir[0] * 0.36f;
		//other->v.origin[1] -= area->v.movedir[1] * 0.36f;
		other->v.origin.z -= area->v.movedir.z * 0.36f;
		ladder_velocity.z = other->v.v_angle.x * 6; // go faster when facing forward
		//printf("up (facing up/forward)  ");

		if (ladder_velocity.z < 90) {
            ladder_velocity.z = 90; // minimum speed
        }
	} else if ((other->v.v_angle.x >= 15) && (forwardspeed < 0)) { // up (facing down)
		//other->v.origin[0] += area->v.movedir[0] * 0.36f;
		//other->v.origin[1] += area->v.movedir[1] * 0.36f;
		other->v.origin.z += area->v.movedir.z * 0.36f;
		//printf("up (facing down)  ");

		ladder_velocity.z = other->v.v_angle.x * 4;
	} else if ((other->v.v_angle.x <= 15) && (forwardspeed < 0)) { // down (facing up/forward)
		//other->v.origin[0] += area->v.movedir[0] * 0.36f;
		//other->v.origin[1] += area->v.movedir[1] * 0.36f;
		other->v.origin.z += area->v.movedir.z * 0.36f;

		ladder_velocity.z = other->v.v_angle.x * -5;// go faster when facing forward
		//printf("down (facing up/forward)  ");

		if (ladder_velocity.z > -80) {
            ladder_velocity.z = -80; // minimum speed
        }
	} else if ((other->v.v_angle.x >= 15) && (forwardspeed > 0)) { // down (facing down)
		other->v.origin.x -= area->v.movedir.x * 0.36f;
		other->v.origin.y -= area->v.movedir.y * 0.36f;
		other->v.origin.z -= area->v.movedir.z * 0.36f;
		//printf("down (facing down)  ");

		ladder_velocity.z = other->v.v_angle.x * -4;
	}

	//printf("angle: %i; velo: %i\n", (int)other->v.v_angle[0], (int)ladder_velocity[2]);

	if (ladder_velocity.z > 100) {
        ladder_velocity.z = 100;
    } else if (ladder_velocity.z < -1 * 100) {
        ladder_velocity.z = -1 * 100;
    }

	other->v.velocity = ladder_velocity;
}