
#include <amxmodx>
#include <cellarray>
#include <engine>
#include <fakemeta>
#include <hamsandwich>
#include <cstrike>
#include <xs>

#define PLUGIN	"Peeping Tom"
#define AUTHOR	"joaquimandrade"
#define VERSION	"1.1 lucia"

#define FLAG_ADMIN ADMIN_KICK

const MaxPlayers = 33;

const Vector=3

const X=0
const Y=1
const Z=2

enum Individual
{
	Spectated,
	Viewed
}

enum OriginOffset
{
	FrameSide,
	FrameTop,
	FrameBottom,
}

enum FramePoint
{
	TopLeft,
	TopRight,
	BottomLeft,
	BottomRight
}

new Float:OriginOffsets[OriginOffset] =  {_:13.0,_:25.0,_:36.0}

new Float:ScaleMultiplier = 0.013;
new Float:ScaleLower = 0.005

new spriteClassName[] = "peeping_tom";

enum Teams
{
	Terrorist,
	CounterTerrorist
}

enum Cvar
{
	ShowTeammates,
	ShowWhileAlive
}

new Cvars[Cvar];

new SpritesPath[Teams][] = {"sprites/peeping_tom/t.spr","sprites/peeping_tom/ct.spr"}

new Teams:TeamIndexes[CsTeams] = {0,_:Terrorist,_:CounterTerrorist,0}

new bool:IsAdmin[MaxPlayers]
new SpectatorsCounter;
new SpectatingUser[MaxPlayers]
new SpectatorsUserCounter[MaxPlayers]
new bool:OnFirstPersonView[MaxPlayers]
new Array:SpritesBySpectated[MaxPlayers]
new Array:SpritesByViewed[MaxPlayers];

new Trie:SpritesSpectators;

new Float:SpriteThinkDelay = 0.01;

new ForwardAddToFullPack
new HamHook:HamPlayerKilled
//new HamHook:HamPlayerSpawn

//new bool:HamPlayerSpawnEnabled

new Float:SomeNonZeroValue = 1.0

new Float:NonVisiblePosition[Vector] = {-1000.0,-1000.0,-1000.0}



/*
tryDisableHamPlayerSpawn()
{
	if(HamPlayerSpawnEnabled)
	{
		DisableHamForward(HamPlayerSpawn)
		HamPlayerSpawnEnabled = false;
	}
}

tryEnableHamPlayerSpawn()
{
	if(!HamPlayerSpawnEnabled)
	{
		EnableHamForward(HamPlayerSpawn)
		HamPlayerSpawnEnabled = true;
	}
}

public nSprites(id)
{
	new ent = -1;
	
	new sprites;
	
	while((ent = find_ent_by_class(ent,spriteClassName)))
		sprites++;
	
	server_print("SPRITES %d",sprites);
	
	return PLUGIN_HANDLED;
}
* */

public plugin_init()
{
	register_plugin(PLUGIN, VERSION, AUTHOR)
	
	register_event("TextMsg","specMode","b","2&#Spec_Mode")
	register_event("StatusValue","specTarget","bd","1=2")
	register_event("SpecHealth2","specTarget","bd")
	
	register_event("HLTV", "newRound", "a", "1=0", "2=0")  
	register_logevent("roundStart", 2, "0=World triggered", "1=Round_Start" )
	
	register_think(spriteClassName,"spriteThink");	
	
	HamPlayerKilled = RegisterHam(Ham_Killed,"player","playerKilled");
	DisableHamForward(HamPlayerKilled);
	
	//HamPlayerSpawn = RegisterHam(Ham_Spawn,"player","playerSpawn");
	//DisableHamForward(HamPlayerSpawn)
	
	//register_clcmd("nSprites","nSprites");
	
	register_cvar("peepingTom_version",VERSION,FCVAR_SERVER|FCVAR_SPONLY);
	
	Cvars[ShowTeammates] = register_cvar("peepingTom_team","1");
	Cvars[ShowWhileAlive] = register_cvar("peepingTom_alive","0");
	
}

public playerSpawn(id)
{
	/* LATER */	
}

public addToFullPack(es, e, ent, host, hostflags, player, pSet)
{	
	new spriteArray[2];
	spriteArray[0] = ent;
	spriteArray[1] = 0;
	
	new Trie:spectators;
	
	if(TrieGetCell(SpritesSpectators,spriteArray,spectators))
	{
		new spectatorArray[2];
		spectatorArray[0] = host;
		spectatorArray[1] = 0;
		
		if(!TrieKeyExists(spectators,spectatorArray))
		{			
			set_es(es, ES_RenderFx,kRenderFxNone)
			set_es(es, ES_RenderMode,kRenderTransAlpha)
			set_es(es, ES_RenderAmt, 0)
		}
	}
}  

public plugin_cfg()
{
	SpritesSpectators = TrieCreate();
}

public spriteThink(sprite)
{
	entity_set_float(sprite,EV_FL_nextthink, get_gametime() + SpriteThinkDelay);
	
	static ID[Individual]
	
	ID[Spectated] = entity_get_edict(sprite,EV_ENT_euser1);
	ID[Viewed] = entity_get_edict(sprite,EV_ENT_euser2);
	
	static Float:origin[Individual][Vector]
	
	entity_get_vector(ID[Spectated],EV_VEC_origin,origin[Spectated])
	entity_get_vector(ID[Viewed],EV_VEC_origin,origin[Viewed])
	
	static Float:diff[Vector]
	static Float:diffAngles[Vector]
	
	xs_vec_sub(origin[Viewed],origin[Spectated],diff)			
	xs_vec_normalize(diff,diff)         
	
	vector_to_angle(diff,diffAngles)
	
	diffAngles[0] = -diffAngles[0];
	
	static Float:framePoints[FramePoint][Vector]
	
	calculateFramePoints(origin[Viewed],framePoints,diffAngles)			
	
	static Float:eyes[Vector]
	
	xs_vec_copy(origin[Spectated],eyes)
	
	static Float:viewOfs[Vector]			
	entity_get_vector(ID[Spectated],EV_VEC_view_ofs,viewOfs);
	xs_vec_add(eyes,viewOfs,eyes);
	
	static Float:framePointsTraced[FramePoint][Vector]
	
	static FramePoint:closerFramePoint
	
	if(traceEyesFrame(ID[Spectated],eyes,framePoints,framePointsTraced,closerFramePoint))
	{
		static Float:otherPointInThePlane[Vector]
		static Float:anotherPointInThePlane[Vector]
		
		static Float:sideVector[Vector]
		static Float:topBottomVector[Vector]
		
		angle_vector(diffAngles,ANGLEVECTOR_UP,topBottomVector)
		angle_vector(diffAngles,ANGLEVECTOR_RIGHT,sideVector)
		
		xs_vec_mul_scalar(sideVector,SomeNonZeroValue,otherPointInThePlane)
		xs_vec_mul_scalar(topBottomVector,SomeNonZeroValue,anotherPointInThePlane)	
		
		xs_vec_add(otherPointInThePlane,framePointsTraced[closerFramePoint],otherPointInThePlane)
		xs_vec_add(anotherPointInThePlane,framePointsTraced[closerFramePoint],anotherPointInThePlane)
		
		static Float:plane[4]
		xs_plane_3p(plane,framePointsTraced[closerFramePoint],otherPointInThePlane,anotherPointInThePlane)
		
		moveToPlane(plane,eyes,framePointsTraced,closerFramePoint);
		
		static Float:middle[Vector]
		
		static Float:half = 2.0
		
		xs_vec_add(framePointsTraced[TopLeft],framePointsTraced[BottomRight],middle)
		xs_vec_div_scalar(middle,half,middle)
		
		new Float:scale = ScaleMultiplier * vector_distance(framePointsTraced[TopLeft],framePointsTraced[TopRight])
		
		if(scale < ScaleLower)
			scale = ScaleLower;
		
		entity_set_float(sprite,EV_FL_scale,scale);
		entity_set_vector(sprite,EV_VEC_angles,diffAngles);				
		entity_set_origin(sprite,middle)
	}
	else
	{
		entity_set_origin(sprite,NonVisiblePosition);
	}
}

public playerKilled(id, idattacker, shouldgib)
{
	deleteSpritesByViewed(id);
}

createSpriteSpectatorsHolder(sprite)
{
	new spriteArray[2];
	spriteArray[0] = sprite;
	spriteArray[1] = 0;
	
	TrieSetCell(SpritesSpectators,spriteArray,TrieCreate());
}

deleteSpriteIfExists(sprite)
{
	new spriteArray[2];
	spriteArray[0] = sprite;
	spriteArray[1] = 0;
	
	if(TrieKeyExists(SpritesSpectators,spriteArray))
	{
		remove_entity(sprite);
		deleteSpriteSpectatorsHolder(sprite);
	}
}

deleteSpriteSpectatorsHolder(sprite)
{
	new spriteArray[2];
	spriteArray[0] = sprite;
	spriteArray[1] = 0;
	
	new Trie:spectators
	
	TrieGetCell(SpritesSpectators,spriteArray,spectators);
	TrieDestroy(spectators);
	TrieDeleteKey(SpritesSpectators,spriteArray);
}

setSpriteSpectatorStatus(sprite,spectator,bool:status)
{
	new spriteArray[2];
	spriteArray[0] = sprite;
	spriteArray[1] = 0;
	
	if(TrieKeyExists(SpritesSpectators,spriteArray))
	{
		new Trie:spectators;
		
		TrieGetCell(SpritesSpectators,spriteArray,spectators)
		
		new spectatorArray[2];
		spectatorArray[0] = spectator;
		spectatorArray[1] = 0;
		
		if(status)
		{
			TrieSetCell(spectators,spectatorArray,true);
		}
		else
		{
			TrieDeleteKey(spectators,spectatorArray);
		}
	}
}

public newRound()
{
	new id,players[32],playerCount
	
	get_players(players, playerCount, "")
	
	for(new i=0;i<playerCount;i++)
	{
		id = players[i];
		
		if(IsAdmin[id])
		{
			checkQuitingFirstPersonView(id)
			deleteSpritesByViewed(id);
		}
	}	
	
	//tryDisableHamPlayerSpawn();
}

public roundStart()
{
	new id,players[32],playerCount
	
	get_players(players, playerCount, "")
	
	for(new i=0;i<playerCount;i++)
	{
		id = players[i];
		
		if(get_pcvar_num(Cvars[ShowWhileAlive]))
		{
			if(IsAdmin[id] && is_user_alive(id))
			{
				SpectatingUser[id] = id;
				joiningFirstPersonView(id);
			}
		}
	}	
	
	//tryEnableHamPlayerSpawn();
}

public client_authorized(id)
{	
	IsAdmin[id] = bool:(get_user_flags(id) & FLAG_ADMIN);
	
	SpritesByViewed[id] = ArrayCreate();
	
	SpectatingUser[id] = 0;
	
	SpectatorsUserCounter[id] = 0
}

public client_disconnect(id)
{
	checkQuitingFirstPersonView(id)	
	deleteSpritesByViewed(id);
}

createSprite(spectated,viewed)
{
	new sprite = create_entity("env_sprite")
	
	assert is_valid_ent(sprite);
	
	entity_set_string(sprite,EV_SZ_classname,spriteClassName);
	entity_set_model(sprite,SpritesPath[TeamIndexes[cs_get_user_team(viewed)]]);
	entity_set_edict(sprite,EV_ENT_euser1,spectated)
	entity_set_edict(sprite,EV_ENT_euser2,viewed)
	
	entity_set_float(sprite,EV_FL_nextthink, get_gametime() + SpriteThinkDelay);
	
	return sprite;
}
createSprites(spectator,spectated)
{	
	if(is_user_connected(spectated))
	{
		new Array:spritesBySpectated = ArrayCreate();

		new i, viewed,players[32],playerCount

		new CsTeams:teamSpectated = cs_get_user_team(spectated);
	
		get_players(players, playerCount, "")
		for (i=0; i<playerCount; i++)
		{	
			viewed = players[i];
			
			if(is_user_alive(viewed) && (viewed != spectated) && (get_pcvar_num(Cvars[ShowTeammates]) || (cs_get_user_team(viewed) != teamSpectated) ) )
			{			
				new sprite = createSprite(spectated,viewed);
				
				createSpriteSpectatorsHolder(sprite);
				
				setSpriteSpectatorStatus(sprite,spectator,true);
				
				ArrayPushCell(spritesBySpectated,sprite);
				ArrayPushCell(SpritesByViewed[viewed],sprite);
			}
		}
		
		SpritesBySpectated[spectated] = spritesBySpectated;
	}
}


deleteSprites(Array:sprites)
{
	new nSprites = ArraySize(sprites);
	
	for(new i=0;i<nSprites;i++)
	{
		new sprite = ArrayGetCell(sprites,i);
		
		deleteSpriteIfExists(sprite)
	}
	
	ArrayDestroy(sprites);
}

deleteSpritesBySpectated(spectated)
{
	deleteSprites(SpritesBySpectated[spectated]);
}

deleteSpritesByViewed(viewed)
{
	deleteSprites(SpritesByViewed[viewed]);
	SpritesByViewed[viewed] = ArrayCreate();
}

setSpritesSpectatorStatus(Array:sprites,spectator,bool:status)
{
	new nSprites = ArraySize(sprites);
	
	for(new i=0;i<nSprites;i++)
	{
		new sprite = ArrayGetCell(sprites,i);
		setSpriteSpectatorStatus(sprite,spectator,status);
	}
}

joiningFirstPersonView(id)
{
	if(!SpectatorsCounter++)
	{
		ForwardAddToFullPack = register_forward(FM_AddToFullPack,"addToFullPack",1);	
		EnableHamForward(HamPlayerKilled);
	}
	
	OnFirstPersonView[id] = true;
	
	new spectated = SpectatingUser[id]
	
	if(!SpectatorsUserCounter[spectated]++)
	{
		createSprites(id,spectated);
	}	
	else
	{
		setSpritesSpectatorStatus(SpritesBySpectated[spectated],id,true)
	}
}

quitingFirstPersonView(id)
{	
	if(!--SpectatorsCounter)
	{
		unregister_forward(FM_AddToFullPack,ForwardAddToFullPack,1);
		DisableHamForward(HamPlayerKilled);
	}
	
	OnFirstPersonView[id] = false;
	
	new spectated = SpectatingUser[id]
	
	if(!--SpectatorsUserCounter[spectated])
	{
		deleteSpritesBySpectated(spectated);
	}
	else
	{
		setSpritesSpectatorStatus(SpritesBySpectated[spectated],id,false)
	}
}
checkQuitingFirstPersonView(id)
{
	if(OnFirstPersonView[id])
	{
		quitingFirstPersonView(id);
	}
}

public specMode(id)
{
	if(IsAdmin[id])
	{	
		new specMode[12]
		read_data(2,specMode,11)
		
		if((specMode[10] == '4') && SpectatingUser[id] && !OnFirstPersonView[id])
		{
			joiningFirstPersonView(id)
		}
		else
		{
			checkQuitingFirstPersonView(id);
		}
	}
}

public specTarget(id)
{
	if(IsAdmin[id])
	{		
		new spectated = read_data(2);
		
		if(spectated)
		{
			if(OnFirstPersonView[id])
			{
				if(spectated != SpectatingUser[id])
				{
					quitingFirstPersonView(id);
					SpectatingUser[id] = spectated;				
					joiningFirstPersonView(id)
				}
			}
			
			SpectatingUser[id] = spectated;	
		}
	}
}

public plugin_precache()
{
	for(new Teams:i = Terrorist;i < Teams;i++)
		precache_model(SpritesPath[i])
}

calculateFramePoints(Float:origin[Vector],Float:framePoints[FramePoint][Vector],Float:perpendicularAngles[Vector])
{
	new Float:sideVector[Vector]
	new Float:topBottomVector[Vector]
	
	angle_vector(perpendicularAngles,ANGLEVECTOR_UP,topBottomVector)
	angle_vector(perpendicularAngles,ANGLEVECTOR_RIGHT,sideVector)
	
	new Float:sideDislocation[Vector]
	new Float:bottomDislocation[Vector]
	new Float:topDislocation[Vector]
	
	xs_vec_mul_scalar(sideVector,Float:OriginOffsets[FrameSide],sideDislocation)
	xs_vec_mul_scalar(topBottomVector,Float:OriginOffsets[FrameTop],topDislocation)	
	xs_vec_mul_scalar(topBottomVector,Float:OriginOffsets[FrameBottom],bottomDislocation)
	
	xs_vec_copy(topDislocation,framePoints[TopLeft])
	
	xs_vec_add(framePoints[TopLeft],sideDislocation,framePoints[TopRight])
	xs_vec_sub(framePoints[TopLeft],sideDislocation,framePoints[TopLeft])
	
	xs_vec_neg(bottomDislocation,framePoints[BottomLeft])
	
	xs_vec_add(framePoints[BottomLeft],sideDislocation,framePoints[BottomRight])
	xs_vec_sub(framePoints[BottomLeft],sideDislocation,framePoints[BottomLeft])
	
	
	for(new FramePoint:i = TopLeft; i <= BottomRight; i++)
		xs_vec_add(origin,framePoints[i],framePoints[i])
	
}

traceEyesFrame(id,Float:eyes[Vector],Float:framePoints[FramePoint][Vector],Float:framePointsTraced[FramePoint][Vector],&FramePoint:closerFramePoint)
{
	new Float:smallFraction = 1.0
	
	for(new FramePoint:i = TopLeft; i <= BottomRight; i++)
	{
		new trace;
		engfunc(EngFunc_TraceLine,eyes,framePoints[i],IGNORE_MONSTERS,id,trace)
		
		new Float:fraction
		get_tr2(trace, TR_flFraction,fraction);
		
		if(fraction == 1.0)
		{
			return false;
		}
		else
		{
			if(fraction < smallFraction)
			{
				smallFraction = fraction
				closerFramePoint = i;
			}
			
			get_tr2(trace,TR_EndPos,framePointsTraced[i]);
		}
	}
	
	return true;
}

moveToPlane(Float:plane[4],Float:eyes[Vector],Float:framePointsTraced[FramePoint][Vector],FramePoint:alreadyInPlane)
{
	new Float:direction[Vector]
	
	for(new FramePoint:i=TopLeft;i<alreadyInPlane;i++)
	{
		xs_vec_sub(eyes,framePointsTraced[i],direction)
		//xs_vec_normalize(direction,direction)         
		xs_plane_rayintersect(plane,framePointsTraced[i],direction,framePointsTraced[i])
	}
	
	for(new FramePoint:i=alreadyInPlane+FramePoint:1;i<=BottomRight;i++)
	{
		xs_vec_sub(eyes,framePointsTraced[i],direction)
		//xs_vec_normalize(direction,direction)         
		xs_plane_rayintersect(plane,framePointsTraced[i],direction,framePointsTraced[i])
	}
}
	