/*
* [U]NITED [G]AMING [C]HEATS Client Hook
*
* Copyright (c) LIQUID     2001-2002
* Copyright (c) ZOOLABUS   2001-2002
* 
* 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., 675 Mass Ave, Cambridge, MA 02139, USA.
* 
* GNU license doesn't cover Engine directory.
* Content of Engine directory is copyrighted (c) 1999, 2000, by Valve LLC
* and it is licensed under Valve's proprietary license (see original HL SDK).
*/

#include <windows.h>
#include <stdlib.h>
#include <math.h>
#undef NDEBUG
#include <assert.h>
#include <memory.h>
#include <map>
#include <vector>
#include <fstream>
#include "engine/wrect.h"
#include "engine/cl_dll.h"
#include "engine/cdll_int.h"
#include "engine/util_vector.h"
#include "engine/cl_entity.h"
#include "common/com_model.h"
#include "common/event_api.h"
#include "engine/pmtrace.h" 
#include "engine/pm_defs.h"
#include "aimbot.h"
#include "cvar.h"
#include "client.h"
#include "interpreter.h"

#define BOUND_VALUE(var,min,max) if((var)>(max)){(var)=(max);};if((var)<(min)){(var)=(min);}

#define M_PI 3.1415926535897923846f

using namespace std;

//===================================================================================
BoneAimbot gAimbot;
#define gAimbot This define is to prevent usage of gAimbot in this file.

extern float mainViewOrigin[3],mainViewAngles[3];

//===================================================================================
void VectorAngles( const float *forward, float *angles )
{
	float	tmp, yaw, pitch;
	
	if (forward[1] == 0 && forward[0] == 0)
	{
		yaw = 0;
		if (forward[2] > 0)
			pitch = 90.0;
		else
			pitch = 270.0;
	}
	else
	{
		if (!cvar.better_pi)	
			yaw = (float)(atan2(forward[1], forward[0]) * 180.0 / 3.14159);
		else		
			yaw = (float)(atan2(forward[1], forward[0]) * 180.0 / M_PI);
		
		if (yaw < 0) yaw += 360.0;

		tmp = (float)sqrt (forward[0]*forward[0] + forward[1]*forward[1]);
		
		if (!cvar.better_pi)		
			pitch = (float)(atan2(forward[2], tmp) * 180 / 3.14159);
		else			
			pitch = (float)(atan2(forward[2], tmp) * 180 / M_PI);
	}
	angles[0] = pitch;
	angles[1] = yaw;
	angles[2] = 0;
}

//====================================================================================
bool pathFree(float* from,float* to)
{
	pmtrace_t tr;
	gEngfuncs.pEventAPI->EV_SetTraceHull( 2 );
	gEngfuncs.pEventAPI->EV_PlayerTrace( from, to, PM_GLASS_IGNORE, me.ent->index, &tr );
	return ( tr.fraction == 1.0 ); 
}

//====================================================================================
bool IsAimingAtMe( int ax )
{
	vec3_t entViewOfs;
	vec3_t localViewOfs;
	cl_entity_s* ent_ax = vPlayers[ax].getEnt();
	cl_entity_s* ent_bx = me.ent;

	if ( ent_ax->curstate.usehull == 0 )
	{
	entViewOfs[0] = ent_ax->origin[0];
	entViewOfs[1] = ent_ax->origin[1];
	entViewOfs[2] = ent_ax->origin[2] + 17;
	}
	else
	{
	entViewOfs[0] = ent_ax->origin[0];
	entViewOfs[1] = ent_ax->origin[1];
	entViewOfs[2] = ent_ax->origin[2] + 12;
	}

	if ( ent_bx->curstate.usehull == 0 )
	{
	localViewOfs[0] = ent_bx->origin[0];
	localViewOfs[1] = ent_bx->origin[1];
	localViewOfs[2] = ent_bx->origin[2] + 17;
	}
	else
	{
	localViewOfs[0] = ent_bx->origin[0];
	localViewOfs[1] = ent_bx->origin[1];
	localViewOfs[2] = ent_bx->origin[2] + 12;
	}

	vec3_t m_vTraceVector;
	vec3_t m_vAimAngles;
	vec3_t m_vTempAngles;

	m_vTraceVector[0] = localViewOfs[0] - entViewOfs[0];
	m_vTraceVector[1] = localViewOfs[1] - entViewOfs[1];
	m_vTraceVector[2] = localViewOfs[2] - entViewOfs[2];

	VectorAngles(m_vTraceVector, m_vAimAngles);

	m_vAimAngles[0] *= -1;

	double variationPitch = m_vAimAngles[0] - ent_ax->angles[0];
	double variationYaw   = m_vAimAngles[1] - ent_ax->angles[1];

	if( variationPitch < 0     )  { variationPitch += 360.0; }
	if( variationYaw   < 0     )  { variationYaw   += 360.0; }
	
	if( variationPitch > 180.0 )  { variationPitch = 360.0 - variationPitch; }
	if( variationYaw   > 180.0 )  { variationYaw   = 360.0 - variationYaw;   }

	if( variationPitch<2 && variationYaw<3 ) { return true; }

	return false;
}

//===================================================================================
static void PredictTarget(int index,float *pred)
{
	if(!cvar.pred == 1)
	{
			cl_entity_s* ent = vPlayers[index].getEnt();
			int  historyIndex = (ent->current_position+HISTORY_MAX-cvar.predback)%HISTORY_MAX;
			
			vec3_t vFromOrigin , vToOrigin , vDeltaOrigin,vPredictedOrigin;

			vFromOrigin = ent->ph[historyIndex].origin;
			vToOrigin   = ent->ph[ent->current_position].origin;
			vDeltaOrigin = vToOrigin - vFromOrigin;

			vDeltaOrigin[0] *= cvar.predahead;
			vDeltaOrigin[1] *= cvar.predahead;
			vDeltaOrigin[2] *= cvar.predahead;

			vPredictedOrigin = ent->origin + vDeltaOrigin;
			VectorCopy(vPredictedOrigin,pred);
	}
	
	if(!cvar.pred == 2)
	{
		vec3_t vPredictedOrigin = vPlayers[index].getEnt()->origin;
		float fTargetTime = gEngfuncs.GetClientTime() - cvar.predtime;
		int iHistIndex = vPlayers[index].getEnt()->current_position + HISTORY_MAX;
		int iMaxIndex = iHistIndex - HISTORY_MAX;;
		int iFoundHistIndex = 0;
		bool bFoundInHistory = false;

		while ( !bFoundInHistory  && iHistIndex > iMaxIndex)
		{
			if ((fTargetTime < (vPlayers[index].getEnt()->ph[iHistIndex % HISTORY_MAX].animtime)) &&  (fTargetTime > (vPlayers[index].getEnt()->ph[(iHistIndex-1) % HISTORY_MAX].animtime)) )
			{
				iFoundHistIndex = (iHistIndex-1) % HISTORY_MAX;
				bFoundInHistory = true;
			}
			iHistIndex--;
		}

		vec3_t vFromOrigin , vToOrigin , vDeltaOrigin;
		float fFromTime,fToTime,fTimeFraction;

		if (bFoundInHistory) 
		{
			int iFoundHistIndexNext = (iFoundHistIndex+1) % HISTORY_MAX;
			vFromOrigin = vPlayers[index].getEnt()->ph[iFoundHistIndex].origin;
			vToOrigin = vPlayers[index].getEnt()->ph[iFoundHistIndexNext].origin;
			fFromTime = vPlayers[index].getEnt()->ph[iFoundHistIndex].animtime;
			fToTime = vPlayers[index].getEnt()->ph[iFoundHistIndexNext].animtime;
			fTimeFraction = 0;
			if ( (fToTime - fFromTime) != 0 ) 
				fTimeFraction = (fTargetTime - fFromTime) / (fToTime - fFromTime);
			vDeltaOrigin = vToOrigin - vFromOrigin;
			vPredictedOrigin = vFromOrigin + (vDeltaOrigin * fTimeFraction);
		}
		else 
		{
			int iCurHInd = vPlayers[index].getEnt()->current_position;
			int iPrevHInd = ((iCurHInd - 1) + HISTORY_MAX) % HISTORY_MAX;
			vFromOrigin = vPlayers[index].getEnt()->ph[iCurHInd].origin;
			vToOrigin = vFromOrigin + (vFromOrigin - vPlayers[index].getEnt()->ph[iPrevHInd].origin);
			fFromTime = vPlayers[index].getEnt()->ph[iCurHInd].animtime;
			fToTime = fFromTime + (fFromTime - vPlayers[index].getEnt()->ph[iPrevHInd].animtime);
			fTimeFraction = 0;
			if ( (fToTime - fFromTime) != 0 ) 
				fTimeFraction = (fTargetTime - fFromTime) / (fToTime - fFromTime);
			if (fTimeFraction > cvar.predmax)
				fTimeFraction = cvar.predmax;
			vDeltaOrigin = vToOrigin - vFromOrigin;
			vPredictedOrigin = vFromOrigin + (vDeltaOrigin * fTimeFraction);
		}
		VectorCopy(vPredictedOrigin,pred);
	}
	else 
	{
		VectorCopy(vPlayers[index].getEnt()->origin,pred);
	}
}

//===================================================================================
double OriginAimbot::GetAimingFov(int ax)
{
	double vec[3], view[3];
	double dot;	
	
	float* origin = vPlayers[ax].getEnt()->origin;
	view[0] = origin[0] - me.pmEyePos[0];
	view[1] = origin[1] - me.pmEyePos[1];
	view[2] = origin[2] - me.pmEyePos[2];

	dot = sqrt(view[0] * view[0] + view[1] * view[1] + view[2] * view[2]);
	dot = 1/dot;

	vec[0] = view[0] * dot;
	vec[1] = view[1] * dot;
	vec[2] = view[2] * dot;
	
	if (!cvar.better_pi)
	{	
		view[0] =  sin((me.viewAngles[1] + 90) * (3.141 / 180));
		view[1] = -cos((me.viewAngles[1] + 90) * (3.141 / 180));
		view[2] = -sin( me.viewAngles[0] * (3.141 / 180));	
	}
	else
	{
		view[0] =  sin((me.viewAngles[1] + 90) * (M_PI / 180));
		view[1] = -cos((me.viewAngles[1] + 90) * (M_PI / 180));
		view[2] = -sin( me.viewAngles[0] * (M_PI / 180));
	}
	
	dot = view[0] * vec[0] + view[1] * vec[1] + view[2] * vec[2];
	
	return dot;
}

//===================================================================================
void OriginAimbot::calcFovangleAndVisibility(int ax)
{ 
	PlayerInfo& r = vPlayers[ax];
	r.fovangle = (float)((1.0-GetAimingFov(ax))*360.0); 
	
	// visibility: 
	if(0) {}
	else if(!GetAimingTeam(ax)) { r.visible = true; }
	else if(r.fovangle>10)      { r.visible = pathFree(vPlayers[ax].getEnt()->origin, me.pmEyePos); }
	else                        { r.visible = (TargetRegion(ax)!=NULL); }
}

//===================================================================================
void OriginAimbot::findTarget()
{
	// target locking
	if( cvar.lock && target!=-1 && vPlayers[target].getPVS() && GetPlayerVisX(target) )
	{
		return;
	}

	// scan for a new target
	target=-1;
	for (int ax=0;ax<vPlayers.size();ax++)
	if  ( 
		  vPlayers[ax].getPVS() 
		  && GetPlayerVisX(ax)     
		  && GetAimingTeam(ax)    
		  && vPlayers[ax].fovangle<cvar.fov 
		 )
	{
		if( target == -1 )                                       { target = ax; continue; }

		switch(cvar.mode)
		{
		default:
		case 0: // distance based 
			if( vPlayers[ax].visible  && !vPlayers[target].visible )  { target = ax; break; }
			if( vPlayers[ax].distance <  vPlayers[target].distance )  { target = ax; break; }
			break;
		case 1: // fov based
			if( vPlayers[ax].visible  && !vPlayers[target].visible )  { target = ax; break; }
			if( GetAimingFov(ax) >   GetAimingFov(target)  )          { target = ax; break; }
			break;
		case 2: // points based
			if( vPlayers[ax].points   >  vPlayers[target].points   )  { target = ax; break; }
			break;
		}
	}
}

//==============================================================================
void OriginAimbot::draw()
{
	for (int ax=0;ax<vPlayers.size();ax++) 
		if (vPlayers[ax].getPVS() ) draw_player_vecs(ax);
}

//==============================================================================
void OriginAimbot::draw_player_vecs(int ax)
{
    // draw history:
	if(cvar.avdraw==2)
	{
		cl_entity_s* ent = vPlayers[ax].getEnt();
		int  historyIndex = ent->current_position;
		for(int i=0;i<HISTORY_MAX;i++)
		{
			gDrawFilledBoxAtLocation(ent->ph[historyIndex].origin,0xFFFFFFFF,1);

			// step back
			--historyIndex; if(historyIndex<0) historyIndex=(HISTORY_MAX-1);
			}
	}

	vec3_t aim_location, target_origin;
	vec3_t up, right, forward, playerAngles;

	vector<aimvec>::iterator pos, end;
	if (vPlayers[ax].getEnt()->curstate.usehull == 0)
	{
			pos = AimVecsS.begin();
			end = AimVecsS.end();
	} else {
			pos = AimVecsD.begin();
			end = AimVecsD.end();
	}


	// get predicted origin
	PredictTarget(ax,target_origin);

	// calculate aiming vectors
	playerAngles[0]=0;
	playerAngles[1]=vPlayers[ax].getEnt()->angles[1];
	playerAngles[2]=0;
	gEngfuncs.pfnAngleVectors (playerAngles, forward, right, up);
	forward[2] = -forward[2];

	register DWORD color = 0xFF1111FF;
	for (;pos!=end;++pos) 
	{
		VectorCopy(target_origin,aim_location);

		aim_location = aim_location + forward * pos->f;
		aim_location = aim_location + up * pos->h;
		aim_location = aim_location + right * pos->r;
		
		gDrawFilledBoxAtLocation(aim_location,color,1);
		color = 0xFFFFFFFF;
	}
}

//====================================================================================
aimvec* OriginAimbot::TargetRegion(int ax)
{
	vec3_t vecEnd, up, right, forward, EntViewOrg,PredOrg, playerAngles;
	PredictTarget(ax,PredOrg);
	
	vector<aimvec>::iterator si, end;
	if (vPlayers[ax].getEnt()->curstate.usehull == 0)
	{
		// loop AimVecsS
		si = AimVecsS.begin(); end = AimVecsS.end();
	} else {
		// loop AimVecsD
		si = AimVecsD.begin(); end = AimVecsD.end();
	}

    // calculate angle vectors
	playerAngles[0]=0;
	playerAngles[1]=vPlayers[ax].getEnt()->angles[1];
	playerAngles[2]=0;
    gEngfuncs.pfnAngleVectors (playerAngles, forward, right, up);
	forward[2] = -forward[2];

	// trace to aim vectors
	for (;si!=end;++si)
	{
		VectorCopy(PredOrg,EntViewOrg);

		EntViewOrg = EntViewOrg + forward * si->f;
		EntViewOrg = EntViewOrg + up * si->h;
        EntViewOrg = EntViewOrg + right * si->r;
		
		if(pathFree(me.pmEyePos,EntViewOrg)) return &(*si);
	}
	return NULL;
}

//==============================================================================
void OriginAimbot::CalcTargetSpot(float *out)
{
	vec3_t vecEnd, up, right, forward, EntViewOrg, playerAngles;
	cl_entity_s* ent = vPlayers[target].getEnt();
	
	// get predicted origin
	PredictTarget(target,out);

	// calculate angle vectors
	playerAngles[0]=0;
	playerAngles[1]=ent->angles[1];
	playerAngles[2]=0;
	gEngfuncs.pfnAngleVectors (playerAngles, forward, right, up);
	
	aimvec* si = TargetRegion(target);

	// calculate h, r, f
	float h=0,r=0,f=0;
	if (si)
	{
		f = si->f;
		h = si->h;
		r = si->r;
	}
	else 
	{
		if (ent->curstate.usehull==0 && AimVecsS.size()>0)
		{
			f = AimVecsS[0].f;
			h = AimVecsS[0].h;
			r = AimVecsS[0].r;
		}
		else
		{
			f = AimVecsD[0].f;
			h = AimVecsD[0].h;
			r = AimVecsD[0].r;
		}
	}
	if( cvar.randmax )
	{
		randomizer.next();
		h += randomizer.h;
		f += randomizer.f;
		r += randomizer.r;
	}
	// add aim vecs. (out=predicted origin)
	out[0] = out[0] + forward[0]*f + up[0]*h + right[0]*r;
	out[1] = out[1] + forward[1]*f + up[1]*h + right[1]*r;
	out[2] = out[2] + forward[2]*f + up[2]*h + right[2]*r;
}

//==============================================================================
void OriginAimbot::CalcVec_MeToTarget(float *view)
{
	float EntViewOrg[3];
	
	CalcTargetSpot(EntViewOrg);
	VectorCopy(EntViewOrg,aim_spot);

	view[0] = EntViewOrg[0] - me.pmEyePos[0];
	view[1] = EntViewOrg[1] - me.pmEyePos[1];
	view[2] = EntViewOrg[2] - me.pmEyePos[2];
}


//==============================================================================
void OriginAimbot::CalcViewAngles()
{
	float vec_to_target[3];
	CalcVec_MeToTarget( vec_to_target);

	VectorAngles(vec_to_target,aim_viewangles);
	aim_viewangles[0] *= -1;

	if (aim_viewangles[0]>180) aim_viewangles[0]-=360;
	if (aim_viewangles[1]>180) aim_viewangles[1]-=360;
 
	if(cvar.smooth!=0.0)
	{
		float diff[3];
		diff[0] = aim_viewangles[0]-me.viewAngles[0];
		diff[1] = aim_viewangles[1]-me.viewAngles[1];
		
		if (diff[0]>180)  diff[0]-=360;
		if (diff[1]>180)  diff[1]-=360;
		if (diff[0]<-180) diff[0]+=360;
		if (diff[1]<-180) diff[1]+=360;

		if     ( diff[0] >  cvar.smooth ) { aim_viewangles[0] = me.viewAngles[0]+cvar.smooth; }
		else if( diff[0] < -cvar.smooth ) { aim_viewangles[0] = me.viewAngles[0]-cvar.smooth; }
		if     ( diff[1] >  cvar.smooth ) { aim_viewangles[1] = me.viewAngles[1]+cvar.smooth; }
		else if( diff[1] < -cvar.smooth ) { aim_viewangles[1] = me.viewAngles[1]-cvar.smooth; }

		if (aim_viewangles[0]>180)  aim_viewangles[0]-=360;
		if (aim_viewangles[1]>180)  aim_viewangles[1]-=360;
		if (aim_viewangles[0]<-180) aim_viewangles[0]+=360;
		if (aim_viewangles[1]<-180) aim_viewangles[1]+=360;
	}
}

//===================================================================================
class DrawPoints
{
public:
	void aggregate(float* _origin, DWORD _color=0xFFFFFFFF, int _size=1);
	void draw_all ();

protected:
	// scheduled draw points
	struct ScheduledDrawPoint
	{
		ScheduledDrawPoint() : active(false) {}
		vec3_t origin;
		DWORD  color_compiled;
		int    size;
		bool   active;
	};
	enum { MAX_DRAW_POINTS=64 };
	std::vector<ScheduledDrawPoint> vecDrawPoints;//(64);
	int DrawPointsNextIndex;

public:
	DrawPoints() : DrawPointsNextIndex(0), vecDrawPoints(MAX_DRAW_POINTS) {}
};

//===================================================================================
void DrawPoints::aggregate(float* _origin, DWORD _color , int _size )
{
	VectorCopy(_origin,vecDrawPoints[DrawPointsNextIndex].origin);
	vecDrawPoints[DrawPointsNextIndex].color_compiled = _color;
	vecDrawPoints[DrawPointsNextIndex].size = _size;
	vecDrawPoints[DrawPointsNextIndex].active = true;

	DrawPointsNextIndex++;
	if( DrawPointsNextIndex>=(int)vecDrawPoints.size() ) DrawPointsNextIndex=0;
}

// ======= DrawAllHudPoints
void DrawPoints::draw_all()
{
	typedef vector<ScheduledDrawPoint> vp;
	for( vp::iterator pos=vecDrawPoints.begin(); pos!=vecDrawPoints.end(); ++pos )
	{
		if( (*pos).active )
		{
			ScheduledDrawPoint& point = (*pos);
			point.active=false;
			gDrawFilledBoxAtLocation(point.origin,point.color_compiled,point.size);
		}
	}
}
// global DrawPoints
static DrawPoints gDrawPoints;

//==========================================================================================
void BoneAimbot::CalcTargetSpot(float* out)
{
	if( vPlayers[target].numTargetSpots && cvar.modelaim )
	{
		ModelBasedAim(out);
	} 
	else 
	{
		OriginAimbot::CalcTargetSpot(out);
	}
}

// ========================================================================
void BoneAimbot::ModelBasedAim(float* TargetVec)
{
	// try first spot:
	int i = firstaimspot;
	if( i<vPlayers[target].numTargetSpots && i>=0 )
	{
		float* testvec = vPlayers[target].TargetSpots[i];
		if(pathFree(me.pmEyePos,testvec))
		{
			VectorCopy(testvec,TargetVec);
			return;
		}
	}

	for(i=0;i<vPlayers[target].numTargetSpots;i++)
	{
		if(i==firstaimspot) continue;

		float* testvec = vPlayers[target].TargetSpots[i];
		if(pathFree(me.pmEyePos,testvec))
		{
			VectorCopy(testvec,TargetVec);
			return;
		}
	}
	VectorCopy(vPlayers[target].TargetSpots[0], TargetVec );
}

//==========================================================================================
BoneAimbot::ModelInfo* BoneAimbot::getModelInfo(const char* arg_name)
{
	assert(arg_name);

	if(hashToInfo.size()>32)
	{
		Con_Echo("&rerror: Model limit reached.");
		return NULL;
	}
	ModelInfo& info = hashToInfo[arg_name];
	info.name = arg_name;
	return &info;
}

//==========================================================================================
void BoneAimbot::draw()
{
	if(cvar.avdraw<=2) { OriginAimbot::draw(); return; };
	gDrawPoints.draw_all();
}

//==============================================================================
void BoneAimbot::load(const char* filename)
{

}

//==============================================================================
void BoneAimbot::save(const char* filename)
{
	ofstream ofs(filename);
	if(!ofs) { Con_Echo( "&w%s&r write error.",filename); return; }
	else     { Con_Echo( "&w%s&g saved."      ,filename); }

	ofs<<"// this file is generated automatically. do not edit!"<<endl;
	ofs<<"mdl clear_models"<<endl;

	for( iterator pos = hashToInfo.begin(); pos!=hashToInfo.end(); ++pos )
	{
		ModelInfo& info = (*pos).second;
		ofs<<endl<<"mdl new "<<info.name<<endl;

		for(int i=0;i<info.numAimspots;i++)
		{
			ModelAimSpot& spot = info.aimspots[i];
			if(spot.boneindex)
				ofs<<"mdl add_spot "<<spot.boneindex<<" "<<spot.boneindex2<<" "<<spot.factor2<<endl;
			else
				ofs<<"mdl add_spot "<<spot.boneindex<<endl;
		}
	}
	ofs<<endl<<"mdl ov "<<offset_first.h<<" "<<offset_first.f<<" "<<offset_first.r<<endl;
}

// ==================================================================
static void calcPredictDelta(cl_entity_s* ent, float *delta)
{
	if (cvar.pred)
	{
			int  historyIndex = (ent->current_position+HISTORY_MAX-cvar.predback)%HISTORY_MAX;
			
			float* vFromOrigin = ent->ph[historyIndex].origin;
			float* vToOrigin   = ent->ph[ent->current_position].origin;
			
			delta[0] = vToOrigin[0] - vFromOrigin[0];
			delta[1] = vToOrigin[1] - vFromOrigin[1];
			delta[2] = vToOrigin[2] - vFromOrigin[2];

			delta[0] *= cvar.predahead;
			delta[1] *= cvar.predahead;
			delta[2] *= cvar.predahead;
	}
	else 
	{
        delta[0]=delta[1]=delta[2]=0;
	}
}

////==============================================================================
void BoneAimbot::PostStudioDrawPlayer( int flags, struct entity_state_s *pplayer )
{
	int ax = pplayer->number;
	if(!vPlayers[ax].getPVS()) return; 
	
	// non visible players return wrong bones!
	if(GetAimingFov(ax) < (40.0-180.0)/-180.0) return; 
	
	cl_entity_s* ent = vPlayers[ax].getEnt();

	model_s* pModel = pIEngineStudio->SetupPlayerModel(ax-1);
	studiohdr_t* pStudioHeader = (studiohdr_t*)pIEngineStudio->Mod_Extradata( pModel );

	// get bone transform 
	typedef float TransformMatrix[ MAXSTUDIOBONES ][ 3 ][ 4 ];
	TransformMatrix*  pbonetransform = (TransformMatrix*)pIEngineStudio->StudioGetBoneTransform();

	// get model info
	if(cvar.avdraw==3) gSetHudMessage(pStudioHeader->name);
	ModelInfo* l_modelInfo = getModelInfo(pStudioHeader->name);
	if(!l_modelInfo) return;

	// grab target spots:
	vec3_t predictDelta; 
	calcPredictDelta(ent, predictDelta );

	for( int i=0;i<l_modelInfo->numAimspots;i++ )
	{
		ModelAimSpot& spot = l_modelInfo->aimspots[i];
		vec3_t&       pos  = vPlayers[ax].TargetSpots[i];

		pos [ 0 ] = (*pbonetransform)[ spot.boneindex  ][ 0 ][ 3 ];
		pos [ 1 ] = (*pbonetransform)[ spot.boneindex  ][ 1 ][ 3 ];
		pos [ 2 ] = (*pbonetransform)[ spot.boneindex  ][ 2 ][ 3 ];

		// interpolate between two bones
		if( spot.boneindex2 ) 
		{
			vec3_t to, diff;
			
			to  [ 0 ] = (*pbonetransform)[ spot.boneindex2 ][ 0 ][ 3 ];
			to  [ 1 ] = (*pbonetransform)[ spot.boneindex2 ][ 1 ][ 3 ];
			to  [ 2 ] = (*pbonetransform)[ spot.boneindex2 ][ 2 ][ 3 ];

			diff = to - pos;
			
			pos[0] += (spot.factor2 * diff[0]);
			pos[1] += (spot.factor2 * diff[1]);
			pos[2] += (spot.factor2 * diff[2]);
		}

		// predict
		pos[0] += predictDelta[0];
		pos[1] += predictDelta[1];
		pos[2] += predictDelta[2];

		// offset first spot
		if(!i)
		{
			vec3_t anglevec, forward, right, up, offset;
			anglevec[0]    = 0;
			anglevec[1]    = pplayer->angles[1];
			anglevec[2]    = 0;
			gEngfuncs.pfnAngleVectors(pplayer->angles,forward,right,up);
	        
			offset = forward*offset_first.f + right*offset_first.r + up*offset_first.h;
			vPlayers[ax].TargetSpots[0][0] += offset[0];
			vPlayers[ax].TargetSpots[0][1] += offset[1];
			vPlayers[ax].TargetSpots[0][2] += offset[2];
		}

		{
			// log for drawing
			if( cvar.avdraw==4 )
			{
				if( i==firstaimspot )  gDrawPoints.aggregate(pos,0xFF1111FF,2);
				else                   gDrawPoints.aggregate(pos,0xFFFFFFFF);
			} else
			if( cvar.avdraw==5 )
			{
				if( i==firstaimspot )  gDrawPoints.aggregate(pos,0xFF1111FF,2);
			}
		}
	}

	// handle Drawing
	if(cvar.avdraw>=3)
	{
		int i = my_curSelectedBone;
		if( i>=0 && i<pStudioHeader->numbones )
		{
			vec3_t pos;
			pos[ 0 ] = (*pbonetransform)[ i ][ 0 ][ 3 ];
			pos[ 1 ] = (*pbonetransform)[ i ][ 1 ][ 3 ];
			pos[ 2 ] = (*pbonetransform)[ i ][ 2 ][ 3 ];
			gDrawPoints.aggregate( pos, 0xFFFFAAFF, 2 );
			
			mstudiobone_t *pbones = (mstudiobone_t *)((byte *)pStudioHeader + pStudioHeader->boneindex);
			char tmp[64];sprintf(tmp,"#%d: %s",i,pbones[i].name);
			gSetHudMessage2(tmp);
		}
	}
	// update info:
	vPlayers[ax].numTargetSpots = l_modelInfo->numAimspots;
}

//==================================================================================
void BoneAimbot::command()
{
	const char* thecommand = cmd.argC(1);

	if(0){}
	else if(!strcmp(thecommand,"first"))
	{
		firstaimspot = cmd.argI(2);
	}
	else if(!strcmp(thecommand,"list"))
	{
		//
		int i=0;
		for(HashToInfoMap::iterator pos=hashToInfo.begin();pos!=hashToInfo.end();++pos,++i)
		{
			Con_Echo("&g#%d: &w%s, spots: %d",i,(*pos).first.c_str(),(*pos).second.numAimspots );
		}
	}
	else if(!strcmp(thecommand,"select"))
	{
		//
		int scan = cmd.argI(2);
		int i=0;
		for(HashToInfoMap::iterator pos=hashToInfo.begin();pos!=hashToInfo.end();++pos,++i)
		{
			if(i==scan)
			{
				Con_Echo("current model: &r%s",(*pos).first.c_str());
				my_curModelInfo = getModelInfo( (*pos).first.c_str() );
				break;
			}
		}
	}
	else if(!strcmp(thecommand,"new"))
	{
		if(*cmd.argC(2))
		{
			my_curModelInfo = getModelInfo( cmd.argC(2) );
		}
	}
	else if(!strcmp(thecommand,"clear_models"))
	{
		my_curModelInfo=NULL;
		hashToInfo.clear();
	}
	else if(!strcmp(thecommand,"set_team"))
	{
		if(!my_curModelInfo) return;
		my_curModelInfo->team = cmd.argI(2);
	}
	else if(!strcmp(thecommand,"add_spot"))
	{
		if(!my_curModelInfo) return;
		int insert_index = my_curModelInfo->numAimspots;
		if( insert_index>=MAX_TARGET_SPOTS )
		{
			Con_Echo( "too many target spots" );
			return;
		}

		int   arg1 = cmd.argI(2);
		int   arg2 = cmd.argI(3);
		float arg3 = cmd.argF(4);
		if( arg1<0 || arg1>=MAXSTUDIOBONES || arg2<0 || arg2>=MAXSTUDIOBONES )
		{
			Con_Echo( "bone index out of bounds" );
			return;
		}

		my_curModelInfo->numAimspots++;
		ModelAimSpot& spot = my_curModelInfo->aimspots[insert_index];

		spot.boneindex  = arg1;
		spot.boneindex2 = arg2;
		spot.factor2    = arg3;
	}
	else if(!strcmp(thecommand,"clear_spots"))
	{
		if(!my_curModelInfo) return;
		my_curModelInfo->numAimspots=0;
	}
	else if(!strcmp(thecommand,"info"))
	{
		if(!my_curModelInfo) return;
		for(int i=0;i<my_curModelInfo->numAimspots;i++)
		{
			ModelAimSpot& spot = my_curModelInfo->aimspots[i];
			if(!spot.boneindex2)
			{
				Con_Echo("&w#%d&a: bone: %d",i,spot.boneindex);
			} 
			else
			{
				Con_Echo("&w#%d&a: %d -> %d , %5.3f",i,spot.boneindex,spot.boneindex2,spot.factor2);
			}
		}
		Con_Echo( "mdl=&w%s&a team=&w%d&a n=&w%d", my_curModelInfo->name.c_str(), my_curModelInfo->team, my_curModelInfo->numAimspots);
	}
	else if(!strcmp(thecommand,"save"))
	{
		string getOgcDirFile(const char* basename);
		save( getOgcDirFile(cmd.argC(2)).c_str() );
	}
	else if(!strcmp(thecommand,"ov"))
	{
		offset_first.h = cmd.argF(2);
		offset_first.f = cmd.argF(3);
		offset_first.r = cmd.argF(4);
	}
	else
	{
		Con_Echo("unrecognized sub-command");
	}
}

//==================================================================================
AimingRandomizer::AimingRandomizer()
	: 
	rad_from(0),
	az_from(0),
	el_from(0),

	rad_to(0),
	el_to (0.05f),
	az_to (0.0f),

	rad(10),
	az (0),
	el (0),

	fraction(0)
	{}

//==================================================
inline float randomFloat(float from, float to)
{
    float onebased =  float(rand()) / float(RAND_MAX);
	return from + (to-from)*onebased;
}

//========================================
void AimingRandomizer::next()
{
	fraction += cvar.randspeed;
	if( fraction > 1)
	{
		// init a new target.
		fraction = 0;

		rad_from = rad_to;
		az_from  = az_to;
		el_from  = el_to;
		
		rad_to  = randomFloat(0,cvar.randmax);
		az_to   = randomFloat(0,2*M_PI);
		el_to   = randomFloat(0,2*M_PI);
	}

	// interpolate:
	rad = rad_from + (rad_to-rad_from)*fraction;
	az  = az_from  + (az_to -az_from )*fraction;
	el  = el_from  + (el_to -el_from )*fraction;

	// elevate:
	h = (float)sin(el);
	
	// rotate
	float rf_len = (float)cos(el);
	r = (float)sin(az)*rf_len;
	f = (float)cos(az)*rf_len;

	// scale
	h *= rad;
	r *= rad;
	f *= rad;
}
