// Copyright (C) 1997 by Ritual Entertainment, Inc.
// All rights reserved.
//
// This source is may not be distributed and/or modified without
// expressly written permission by Ritual Entertainment, Inc.
//
// DESCRIPTION:
// Camera.  Duh.
//

#include "g_local.h"
#include "entity.h"
#include "trigger.h"
#include "camera.h"
#include "bspline.h"
#include "player.h"
#include "camera.h"

cvar_t *sv_showcameras;

Event EV_Camera_FollowingPath( "followingpath" );
Event EV_Camera_StartMoving( "start" );
Event EV_Camera_Pause( "pause" );
Event EV_Camera_Continue( "continue" );
Event EV_Camera_StopMoving( "stop" );
Event EV_Camera_SetSpeed( "speed" );
Event EV_Camera_SetDistance( "distance" );
Event EV_Camera_SetHeight( "height" );
Event EV_Camera_SetYaw( "yaw" );
Event EV_Camera_FixedYaw( "fixedyaw" );
Event EV_Camera_RelativeYaw( "relativeyaw" );
Event EV_Camera_SetFOV( "fov" );
Event EV_Camera_Orbit( "orbit" );
Event EV_Camera_Follow( "follow" );
Event EV_Camera_Watch( "watch" );
Event EV_Camera_LookAt( "lookat" );
Event EV_Camera_TurnTo( "turnto" );
Event EV_Camera_MoveToEntity( "moveto" );
Event EV_Camera_MoveToPos( "movetopos" );
Event EV_Camera_NoWatch( "nowatch" );
Event EV_Camera_IgnoreAngles( "ignoreangles" );
Event EV_Camera_UseAngles( "useangles" );
Event EV_Camera_SplineAngles( "splineangles" );
Event EV_Camera_NormalAngles( "normalangles" );
Event EV_Camera_FixedPosition( "fixedposition" );
Event EV_Camera_NoFixedPosition( "nofixedposition" );
Event EV_Camera_JumpTime( "jumptime" );
Event EV_Camera_JumpCut( "jumpcut" );
Event EV_Camera_Pan( "pan" );
Event EV_Camera_StopPan( "stoppan" );
Event EV_Camera_PanSpeed( "panspeed" );
Event EV_Camera_PanMax( "panmax" );
Event EV_Camera_SetPanAngles( "setpanangles" );
Event EV_Camera_SetNextCamera( "nextcamera" );
Event EV_Camera_SetOverlay( "setoverlay" );
Event EV_Camera_SetThread( "setthread" );

/*****************************************************************************/
/*SINED func_camera (1 0 0) ? ORBIT START_ON PAN

   Camera used for cinematic sequences.  Start

   "target" points to the target to orbit or follow.  If it points to a path, the camera will follow the path.
   "distance" the distance to follow or orbit if the target is not a path. (default 128).
   "speed" specifies how fast to move on the path or orbit.  (default 1).
   "fov" specifies fov of camera, default 90.
   "yaw" specifies yaw of camera, default 0.
   "height" specifies height of camera from origin, default 128.
   "panspeed" speed at which to pan ( 7 degrees per second )
   "panmax" maximum angle offset for panning ( 45 degrees )
   "nextcamera" a link to the next camera in a chain of cameras
   "overlay" an overlay to use while looking through the camera
   "thread" a thread label that will be fired when the camera is looked through

   ORBIT tells the camera to create a circular path around the object it points to.  It the camera points to a path, it will loop when it gets to the end of the path.
   START_ON causes the camera to be moving as soon as it is spawned.
   PAN camera should pan from right to left

   /*****************************************************************************/

CLASS_DECLARATION( Entity, Camera, "func_camera" );

ResponseDef Camera::Responses[] =
{
	{ &EV_Camera_FollowingPath,  ( Response )Camera::FollowingPath },
	{ &EV_Activate,                 ( Response )Camera::StartMoving },
	{ &EV_Camera_StartMoving,       ( Response )Camera::StartMoving },
	{ &EV_Camera_StopMoving,        ( Response )Camera::StopMoving },
	{ &EV_Camera_Pause,             ( Response )Camera::Pause },
	{ &EV_Camera_Continue,          ( Response )Camera::Continue },
	{ &EV_Camera_SetSpeed,          ( Response )Camera::SetSpeed },
	{ &EV_Camera_SetDistance,       ( Response )Camera::SetDistance },
	{ &EV_Camera_SetHeight,         ( Response )Camera::SetHeight },
	{ &EV_Camera_SetYaw,                ( Response )Camera::SetYaw },
	{ &EV_Camera_FixedYaw,          ( Response )Camera::FixedYaw },
	{ &EV_Camera_RelativeYaw,       ( Response )Camera::RelativeYaw },
	{ &EV_Camera_SetFOV,                ( Response )Camera::SetFOV },
	{ &EV_Camera_Orbit,             ( Response )Camera::OrbitEvent },
	{ &EV_Camera_Follow,                ( Response )Camera::FollowEvent },
	{ &EV_Camera_Watch,             ( Response )Camera::WatchEvent },
	{ &EV_Camera_NoWatch,           ( Response )Camera::NoWatchEvent },
	{ &EV_Camera_LookAt,                ( Response )Camera::LookAt },
	{ &EV_Camera_TurnTo,                ( Response )Camera::TurnTo },
	{ &EV_Camera_MoveToEntity,      ( Response )Camera::MoveToEntity },
	{ &EV_Camera_MoveToPos,         ( Response )Camera::MoveToPos },
	{ &EV_Camera_IgnoreAngles,      ( Response )Camera::IgnoreAngles },
	{ &EV_Camera_UseAngles,        ( Response )Camera::UseAngles },
	{ &EV_Camera_SplineAngles,      ( Response )Camera::SplineAngles },
	{ &EV_Camera_NormalAngles,      ( Response )Camera::NormalAngles },
	{ &EV_Camera_FixedPosition, ( Response )Camera::FixedPosition },
	{ &EV_Camera_NoFixedPosition,   ( Response )Camera::NoFixedPosition },
	{ &EV_Camera_JumpCut,         ( Response )Camera::JumpCut },
	{ &EV_Camera_JumpTime,        ( Response )Camera::JumpTime },
	{ &EV_Camera_Pan,             ( Response )Camera::PanEvent },
	{ &EV_Camera_StopPan,          ( Response )Camera::StopPanEvent },
	{ &EV_Camera_PanSpeed,         ( Response )Camera::PanSpeedEvent },
	{ &EV_Camera_PanMax,           ( Response )Camera::PanMaxEvent },
	{ &EV_Camera_SetPanAngles,    ( Response )Camera::SetPanAngles },
	{ &EV_Camera_SetNextCamera,   ( Response )Camera::SetNextCamera },
	{ &EV_Camera_SetOverlay,      ( Response )Camera::SetOverlay },
	{ &EV_Camera_SetThread,       ( Response )Camera::SetThread },

	{ NULL, NULL }
};
Camera::Camera()
{
	Vector ang;

	default_fov = G_GetFloatArg( "fov", 90 );
	if ( default_fov <= 0 )
		default_fov = 90;
	default_yaw = G_GetFloatArg( "yaw", 0 );

	default_follow_dist = G_GetFloatArg( "distance", 128 );
	default_height = G_GetFloatArg( "height", 128 );
	default_speed = G_GetFloatArg( "speed", 1 );

	default_pan_speed = G_GetFloatArg( "panspeed", 7 );
	default_pan_max = G_GetFloatArg( "panmax", 45 );

	nextCamera = G_GetStringArg( "nextcamera" );
	overlay = G_GetStringArg( "overlay" );
	thread = G_GetStringArg( "thread" );

	watchTime = 0;
	followTime = 0;

	targetEnt = NULL;
	targetWatchEnt = NULL;

	fov = default_fov;

	jumpTime = 2.0f;

	setSolidType( SOLID_NOT );
	setMoveType( MOVETYPE_NONE );

	ang = G_GetVectorArg( "angles", vec_zero );
	setAngles( ang );
	default_angles = ang;

	InitializeState( currentstate );
	InitializeState( newstate );

	sv_showcameras = gi.cvar( "sv_showcameras", "0", 0 );
	showcamera = sv_showcameras->value;
	if ( showcamera )
	{
		setModel( "xyz.def" );
		showModel();
	}
	else
	{
		hideModel();
	}

	if ( spawnflags & START_ON )
	{
		PostEvent( EV_Activate, FRAMETIME );
	}
}

void Camera::InitializeMoveState( CameraMoveState &movestate )
{
	movestate.pos = worldorigin;
	movestate.followEnt = NULL;
	movestate.orbitEnt = NULL;

	movestate.followingpath = false;
	movestate.cameraTime = 0;
	movestate.cameraPath.Clear();

	movestate.fov = default_fov;
	movestate.fixed_position = false;

	movestate.follow_dist = default_follow_dist;
	movestate.follow_mask = MASK_SOLID;
	movestate.height = default_height;
	movestate.speed = default_speed;
}

void Camera::InitializeWatchState( CameraWatchState &watchstate )
{
	worldangles.AngleVectors( &watchstate.dir, NULL, NULL );
	watchstate.watchEnt = NULL;

	watchstate.ignoreangles = false;
	watchstate.splineangles = true;
	watchstate.panning = false;

	watchstate.pan_offset = 0;
	watchstate.pan_dir = 1;
	watchstate.pan_max = default_pan_max;
	watchstate.pan_speed = default_pan_speed;
	watchstate.pan_angles = default_angles;

	watchstate.yaw = default_yaw;
	watchstate.fixedyaw = false;
}

void Camera::InitializeState( CameraState &state )
{
	InitializeMoveState( state.move );
	InitializeWatchState( state.watch );
}


#define DELTA 1e-6

void Camera::EvaluatePosition( CameraState &state )
{
	Vector oldpos, olddir;
	float speed_multiplier;
	Vector prevpos;

	prevpos = state.move.pos;

	olddir = state.watch.dir;

	//
	// evaluate position
	//
	if ( state.move.followingpath )
	{
		speed_multiplier = state.move.cameraPath.Eval( state.move.cameraTime, oldpos, olddir );

		state.move.cameraTime += FRAMETIME * state.move.speed * speed_multiplier;

		if ( state.move.orbitEnt )
		{
			oldpos += state.move.orbitEnt->worldorigin;
		}
	}
	else
	{
		if ( !state.move.followEnt )
		{
			oldpos = state.move.pos;
		}
		else
		{
			trace_t trace;
			Vector start, end, ang, back, temp;
			const gravityaxis_t &grav = gravity_axis[ state.move.followEnt->gravaxis ];

			start = state.move.followEnt->worldorigin;
			start[ grav.z ] += state.move.followEnt->maxs[ 2 ];

			if ( state.watch.fixedyaw )
			{
				ang = vec_zero;
			}
			else
			{
				if ( state.move.followEnt->isSubclassOf( Player ) )
				{
					Entity * ent;
					ent = state.move.followEnt;
					ang = ( ( Player * )ent )->v_angle;
				}
				else
				{
					ang = state.move.followEnt->worldangles;
				}
			}
			ang.y += state.watch.yaw;
			ang.AngleVectors( &temp, NULL, NULL );
			back[ grav.x ] = temp[ 0 ];
			back[ grav.y ] = temp[ 1 ] * grav.sign;
			back[ grav.z ] = temp[ 2 ] * grav.sign;

			end = start - back * state.move.follow_dist;
			end[ 2 ] += 24;

			trace = G_Trace( start, vec_zero, vec_zero, end, state.move.followEnt, state.move.follow_mask, "Camera::EvaluatePosition" );
			//dir = start - trace.endpos;
			//dir.normalize();

			end = trace.endpos;
			oldpos = end + back * 16;
		}
	}
	//
	// evaluate old orientation
	//
	if ( state.watch.watchEnt )
	{
		Vector watchPos;

		watchPos.x = state.watch.watchEnt->worldorigin.x;
		watchPos.y = state.watch.watchEnt->worldorigin.y;
		watchPos.z = state.watch.watchEnt->absmax.z;
		if ( state.move.followEnt == state.watch.watchEnt )
		{
			olddir = watchPos - oldpos;
		}
		else
		{
			olddir = watchPos - worldorigin;
		}
	}
	else
	{
		if ( state.watch.ignoreangles )
		{
			olddir = state.watch.dir;
		}
		else if ( state.move.followingpath )
		{
			if ( !state.watch.splineangles )
			{
				olddir = oldpos - prevpos;
			}
			else
			{
				Vector dir;

				dir = olddir;
				dir.AngleVectors( &olddir, NULL, NULL );
			}
		}
		else if ( state.move.followEnt )
		{
			Vector start;

			start = state.move.followEnt->worldorigin;
			start[ 2 ] += state.move.followEnt->maxs[ 2 ];
			olddir = oldpos - start;
		}
		else if ( state.watch.panning )
		{
			Vector ang;
			state.watch.pan_offset += FRAMETIME * state.watch.pan_speed * state.watch.pan_dir;
			if ( state.watch.pan_offset > state.watch.pan_max )
			{
				state.watch.pan_offset = state.watch.pan_max;
				state.watch.pan_dir = -state.watch.pan_dir;
			}
			else if ( state.watch.pan_offset < -state.watch.pan_max )
			{
				state.watch.pan_offset = -state.watch.pan_max;
				state.watch.pan_dir = -state.watch.pan_dir;
			}

			ang = state.watch.pan_angles;
			ang[ YAW ] += state.watch.pan_offset;
			ang.AngleVectors( &olddir, NULL, NULL );
		}
	}
	olddir.normalize();

	if ( !state.move.fixed_position )
		state.move.pos = oldpos;
	state.watch.dir = olddir;
}



void Camera::FollowingPath( Event *ev )
{
	Vector pos;
	Vector dir;
	float len;

	//
	// evaluate position
	//
	if ( followTime || watchTime )
	{
		int i;
		float t;

		EvaluatePosition( currentstate );
		EvaluatePosition( newstate );

		if ( followTime )
		{
			t = followTime - level.time;
			if ( t < 0 )
			{
				t = 0;
				currentstate.move = newstate.move;
				InitializeMoveState( newstate.move );
				followTime = 0;
			}
			//
			// we want the transition to happen over 2 seconds
			//
			t = ( jumpTime - t ) / jumpTime;

			for ( i = 0; i < 3; i++ )
			{
				pos[ i ] = currentstate.move.pos[ i ] + ( t * ( newstate.move.pos[ i ] - currentstate.move.pos[ i ] ) );
			}
			fov = currentstate.move.fov + ( t * ( newstate.move.fov - currentstate.move.fov ) );
		}
		else
		{
			fov = currentstate.move.fov;
			pos = currentstate.move.pos;
		}

		if ( watchTime )
		{
			t = watchTime - level.time;
			if ( t < 0 )
			{
				t = 0;
				currentstate.watch = newstate.watch;
				InitializeWatchState( newstate.watch );
				watchTime = 0;
			}
			//
			// we want the transition to happen over 2 seconds
			//
			t = ( jumpTime - t ) / jumpTime;

			dir = LerpVector( currentstate.watch.dir, newstate.watch.dir, t );
		}
		else
		{
			dir = currentstate.watch.dir;
		}
	}
	else
	{
		EvaluatePosition( currentstate );
		fov = currentstate.move.fov;
		pos = currentstate.move.pos;
		dir = currentstate.watch.dir;
		//warning("FollowingPath","%p pos x%.2f y%.2f z%2.f time %.2f", this, pos.x, pos.y, pos.z, level.time );
	}

	setOrigin( pos );

	len = dir.length();
	if ( len > 0.05 )
	{
		dir *= ( 1 / len );
		angles = dir.toAngles();
		angles[ PITCH ] = -angles[ PITCH ];
		setAngles( angles );
		//warning("FollowingPath","%p angles x%.2f y%.2f z%2.f time %.2f", this, angles.x, angles.y, angles.z, level.time );
	}


	if ( sv_showcameras->value != showcamera )
	{
		showcamera = sv_showcameras->value;
		if ( showcamera )
		{
			setModel( "xyz.def" );
			showModel();
		}
		else
		{
			hideModel();
		}
	}

	if ( sv_showcameras->value != showcamera )
	{
		showcamera = sv_showcameras->value;
		if ( showcamera )
		{
			setModel( "xyz.def" );
			showModel();
		}
		else
		{
			hideModel();
		}
	}
	if ( showcamera && currentstate.move.followingpath )
	{
		G_Color3f( 1, 1, 0 );
		if ( currentstate.watch.watchEnt )
		{
			currentstate.move.cameraPath.DrawCurve( currentstate.watch.watchEnt->worldorigin, 10 );
		}
		else
		{
			currentstate.move.cameraPath.DrawCurve( 10 );
		}
	}


	PostEvent( EV_Camera_FollowingPath, FRAMETIME );
}

void Camera::LookAt( Event *ev )
{
	Vector pos, delta;
	float len;
	Entity * ent;

	ent = ev->GetEntity( 1 );

	if ( !ent )
		return;

	pos.x = ent->worldorigin.x;
	pos.y = ent->worldorigin.y;
	pos.z = ent->absmax.z;
	delta = pos - worldorigin;
	delta.normalize();
	currentstate.watch.dir = delta;

	len = currentstate.watch.dir.length();
	if ( len > 0.05 )
	{
		angles = currentstate.watch.dir.toAngles();
		angles[ PITCH ] = -angles[ PITCH ];
		setAngles( angles );
	}
}

void Camera::TurnTo( Event *ev )
{
	Vector ang;

	ang = ev->GetVector( 1 );
	ang.AngleVectors( &currentstate.watch.dir, NULL, NULL );
	setAngles( ang );
}

void Camera::MoveToEntity( Event *ev )
{
	Entity * ent;

	ent = ev->GetEntity( 1 );
	if ( ent )
		currentstate.move.pos = ent->worldorigin;
	setOrigin( currentstate.move.pos );
}

void Camera::MoveToPos( Event *ev )
{
	currentstate.move.pos = ev->GetVector( 1 );
	setOrigin( currentstate.move.pos );
}

void Camera::Stop( void )
{
	if ( followTime )
	{
		currentstate.move = newstate.move;
		InitializeMoveState( newstate.move );
	}
	if ( watchTime )
	{
		currentstate.watch = newstate.watch;
		InitializeWatchState( newstate.watch );
	}
	CancelEventsOfType( moveevent );
//	moveevent = NullEvent;
	watchTime = 0;
	followTime = 0;
}

void Camera::CreateOribit( Vector pos,float radius )
{
	newstate.move.cameraPath.Clear();
	newstate.move.cameraPath.SetType( SPLINE_LOOP );

	newstate.move.cameraPath.AppendControlPoint( pos + Vector( radius, 0, 0 ) );
	newstate.move.cameraPath.AppendControlPoint( pos + Vector( 0, radius, 0 ) );
	newstate.move.cameraPath.AppendControlPoint( pos + Vector( -radius, 0, 0 ) );
	newstate.move.cameraPath.AppendControlPoint( pos + Vector( 0, -radius, 0 ) );
}

void Camera::CreatePath( SplinePath *path,splinetype_t type )
{
	SplinePath  *node;
	SplinePath  *loop;

	newstate.move.cameraPath.Clear();
	newstate.move.cameraPath.SetType( type );

	node = path;
	while( node != NULL )
	{
		newstate.move.cameraPath.AppendControlPoint( node->worldorigin, node->angles, node->speed );
		loop = node->GetLoop();
		if ( loop )
		{
			newstate.move.cameraPath.SetLoopPoint( loop->worldorigin );
		}
		node = node->GetNext();

		if ( node == path )
		{
			break;
		}
	}
}

void Camera::FollowPath( SplinePath *path,qboolean loop,Entity * watch )
{
	Stop();
	if ( loop )
	{
		CreatePath( path, SPLINE_LOOP );
	}
	else
	{
		CreatePath( path, SPLINE_CLAMP );
	}

	newstate.move.cameraTime = -2;
	newstate.move.followingpath = true;
	followTime = level.time + jumpTime;
	watchTime = level.time + jumpTime;

	if ( watch )
	{
		newstate.watch.watchEnt = watch;
	}
	else
	{
		newstate.watch.watchEnt = NULL;
	}

	moveevent = EV_Camera_FollowingPath;
	PostEvent( EV_Camera_FollowingPath, FRAMETIME );
}

void Camera::Orbit( Entity *ent,float dist,Entity *watch )
{
	Stop();
	CreateOribit( Vector( 0, 0, newstate.move.height ), dist );
	newstate.move.cameraTime = -2;
	newstate.move.orbitEnt = ent;
	newstate.move.followingpath = true;
	followTime = level.time + jumpTime;

	watchTime = level.time + jumpTime;
	if ( watch )
	{
		newstate.watch.watchEnt = watch;
	}
	else
	{
		newstate.watch.watchEnt = ent;
	}

	moveevent = EV_Camera_FollowingPath;
	PostEvent( EV_Camera_FollowingPath, FRAMETIME );
}

void Camera::FollowEntity( Entity *ent,float dist,int mask,Entity *watch )
{
	assert( ent );

	Stop();

	if ( ent )
	{
		newstate.move.followEnt = ent;
		newstate.move.followingpath = false;
		followTime = level.time + jumpTime;
		watchTime = level.time + jumpTime;
		if ( watch )
		{
			newstate.watch.watchEnt = watch;
		}
		else
		{
			newstate.watch.watchEnt = ent;
		}
		newstate.move.follow_dist = dist;
		newstate.move.follow_mask = mask;
		moveevent = EV_Camera_FollowingPath;
		PostEvent( EV_Camera_FollowingPath, 0 );
	}
}

void Camera::StartMoving( Event *ev )
{
	Entity *ent;
	SplinePath *path;
	int num;

	if ( !targetEnt )
	{
		num = G_FindTarget( 0, Target() );
		ent = G_GetEntity( num );
		if ( !num || !ent )
		{
			if ( spawnflags & PANNING )
			{
				currentstate.watch.panning = true;
				moveevent = EV_Camera_FollowingPath;
				PostEvent( EV_Camera_FollowingPath, FRAMETIME );
				return;
			}
			//
			// we took this out just because of too many warnings, oh well
			//
			//warning("StartMoving", "Can't find target for camera\n" );
			return;
		}
	}
	else
	{
		ent = targetEnt;
	}

	if ( ent->isSubclassOf( SplinePath ) )
	{
		path = ( SplinePath * )ent;
		FollowPath( path, spawnflags & ORBIT, targetWatchEnt );
	}
	else
	{
		if ( spawnflags & ORBIT )
		{
			Orbit( ent, newstate.move.follow_dist, targetWatchEnt );
		}
		else
		{
			FollowEntity( ent, newstate.move.follow_dist, newstate.move.follow_mask, targetWatchEnt );
		}
	}
}

void Camera::StopMoving( Event *ev )
{
	Stop();
}

void Camera::Pause( Event *ev )
{
	CancelEventsOfType( moveevent );
}

void Camera::Continue( Event *ev )
{
	if ( ( int )moveevent != ( int )NullEvent )
	{
		CancelEventsOfType( moveevent );
		PostEvent( moveevent, 0 );
	}
}

void Camera::SetSpeed( Event *ev )
{
	newstate.move.speed = ev->GetFloat( 1 );
}

void Camera::SetDistance( Event *ev )
{
	newstate.move.follow_dist = ev->GetFloat( 1 );
}

void Camera::SetCurrentDistance( float dist )
{
	currentstate.move.follow_dist = dist;
}

void Camera::SetHeight( Event *ev )
{
	newstate.move.height = ev->GetFloat( 1 );
}

void Camera::SetYaw( Event *ev )
{
	newstate.watch.yaw = ev->GetFloat( 1 );
}

void Camera::FixedYaw( Event *ev )
{
	newstate.watch.fixedyaw = true;
}

void Camera::RelativeYaw( Event *ev )
{
	newstate.watch.fixedyaw = false;
}

void Camera::IgnoreAngles( Event *ev )
{
	newstate.watch.ignoreangles = true;
}

void Camera::UseAngles( Event *ev )
{
	newstate.watch.ignoreangles = false;
}

void Camera::SplineAngles( Event *ev )
{
	newstate.watch.splineangles = true;
}

void Camera::NormalAngles( Event *ev )
{
	newstate.watch.splineangles = false;
}

void Camera::FixedPosition( Event *ev )
{
	newstate.move.fixed_position = true;
}

void Camera::NoFixedPosition( Event *ev )
{
	newstate.move.fixed_position = false;
}

void Camera::PanEvent( Event *ev )
{
	currentstate.watch.panning = true;
}

void Camera::StopPanEvent( Event *ev )
{
	currentstate.watch.panning = false;
}

void Camera::PanSpeedEvent( Event *ev )
{
	currentstate.watch.pan_speed = ev->GetFloat( 1 );
}

void Camera::PanMaxEvent( Event *ev )
{
	currentstate.watch.pan_max = ev->GetFloat( 1 );
}

void Camera::SetPanAngles( Event *ev )
{
	if ( ev->NumArgs() > 0 )
	{
		currentstate.watch.pan_angles = ev->GetVector( 1 );
	}
	else
	{
		currentstate.watch.pan_angles = worldangles;
	}
}

void Camera::SetNextCamera( Event *ev )
{
	nextCamera = ev->GetString( 1 );
}

void Camera::SetOverlay( Event *ev )
{
	overlay = ev->GetString( 1 );
}

void Camera::JumpCut( Event *ev )
{
	if ( followTime )
	{
		currentstate.move = newstate.move;
		InitializeMoveState( newstate.move );
		followTime = 0;
	}
	if ( watchTime )
	{
		currentstate.watch = newstate.watch;
		InitializeWatchState( newstate.watch );
		watchTime = 0;
	}
	if ( moveevent )
	{
		CancelEventsOfType( moveevent );
		ProcessEvent( Event( moveevent ) );
	}
}

void Camera::JumpTime( Event *ev )
{
	float t;
	float newjumptime;

	newjumptime = ev->GetFloat( 1 );
	if ( followTime )
	{
		t = ( jumpTime - ( level.time - followTime ) ) / jumpTime;
		followTime = level.time + ( t * newjumptime );
	}
	if ( watchTime )
	{
		t = ( jumpTime - ( level.time - watchTime ) ) / jumpTime;
		watchTime = level.time + ( t * newjumptime );
	}
	jumpTime = newjumptime;
}

void Camera::OrbitEvent( Event *ev )
{
	Entity *ent;

	spawnflags |= ORBIT;
	ent = ev->GetEntity( 1 );
	if ( ent )
	{
		targetEnt = ent;
		targetWatchEnt = NULL;
		if ( ev->NumArgs() > 1 )
			targetWatchEnt = ev->GetEntity( 2 );
		if ( moveevent )
		{
			Stop();
		}
		ProcessEvent( EV_Activate );
	}
}

void Camera::FollowEvent( Event *ev )
{
	Entity *ent;

	spawnflags &= ~ORBIT;
	ent = ev->GetEntity( 1 );
	if ( ent )
	{
		targetEnt = ent;
		targetWatchEnt = NULL;
		if ( ev->NumArgs() > 1 )
			targetWatchEnt = ev->GetEntity( 2 );
		if ( moveevent )
		{
			Stop();
		}
		ProcessEvent( EV_Activate );
	}
}

void Camera::SetFOV( Event *ev )
{
	currentstate.move.fov = ev->GetFloat( 1 );
}

void Camera::WatchEvent( Event *ev )
{
	watchTime = level.time + jumpTime;
	newstate.watch.watchEnt = ev->GetEntity( 1 );
}

void Camera::NoWatchEvent( Event *ev )
{
	watchTime = level.time + jumpTime;
	newstate.watch.watchEnt = NULL;
}

void SetCamera( Entity *ent )
{
	int j;
	edict_t     *other;

	for( j = 1; j <= game.maxclients; j++ )
	{
		other = &g_edicts[ j ];
		if ( other->inuse && other->client )
		{
			Player * client;
			client = ( Player * )other->entity;
			client->SetCamera( ent );
		}
	}
}

str &Camera::NextCamera( void )
{
	return nextCamera;
}

str &Camera::Overlay( void )
{
	return overlay;
}

void Camera::SetThread( Event *ev )
{
	thread = ev->GetString( 1 );
}

str &Camera::Thread( void )
{
	return thread;
}

CLASS_DECLARATION( Camera, SecurityCamera, "func_securitycamera" );

ResponseDef SecurityCamera::Responses[] =
{
	{ NULL, NULL }
};

SecurityCamera::SecurityCamera()
{
	setModel( "camera.def" );
	showModel();
}
