//-----------------------------------------------------------------------------
//
//  $Logfile:: /Quake 2 Engine/Sin/code/game/camera.cpp                       $
// $Revision:: 52                                                             $
//   $Author:: Markd                                                          $
//     $Date:: 11/02/99 12:09p                                                $
//
// 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.
//
// $Log:: /Quake 2 Engine/Sin/code/game/camera.cpp                            $
// 
// 52    11/02/99 12:09p Markd
// Fixed CTF cheat bug
// 
// 51    10/28/99 10:43a Markd
// made a bunch of console commands cheat protected
// 
// 50    5/19/99 4:57p Markd
// fixed some errors
// 
// 49    5/19/99 11:30a Markd
// Added new camera support 
// 
// 48    3/19/99 8:58p Aldie
// Added SetCurrentDistance
// 
// 47    11/14/98 2:33a Markd
// put in some better jump cutting
// 
// 46    11/13/98 11:00p Jimdose
// Took EV_CONSOLE off of fov
// 
// 45    11/13/98 10:50p Markd
// fixed watch camera
// 
// 44    11/10/98 7:06p Markd
// fixed 3rd person camera
// 
// 43    10/24/98 12:42a Markd
// changed origins to worldorigins where appropriate
// 
// 42    10/21/98 1:18a Markd
// took warning out of camera
// 
// 41    10/19/98 12:07a Jimdose
// made all code use fast checks for inheritance (no text lookups when
// possible)
// isSubclassOf no longer requires ::_classinfo()
// 
// 40    10/12/98 5:02p Markd
// Changed fov event
// 
// 39    9/30/98 10:13p Markd
// put in an additional comment line
// 
// 38    9/24/98 1:46a Markd
// fixed camera lerping
// 
// 37    9/20/98 6:49p Markd
// Added Thread ability to cameras when they are looked through
// 
// 36    9/07/98 4:36p Markd
// fixing camera stuff
// 
// 35    8/29/98 9:39p Jimdose
// Added call info to G_Trace
// 
// 34    8/29/98 5:27p Markd
// added specialfx, replaced misc with specialfx where appropriate
// 
// 33    8/27/98 9:01p Jimdose
// Changed grav to a reference
// 
// 32    8/24/98 6:51p Jimdose
// Fixed inverted gravity axis
// 
// 31    8/22/98 9:35p Markd
// When stopping the camera make sure to check if followtime or watchtime are
// non-zero, if so copy over the new states
// 
// 30    8/22/98 8:48p Jimdose
// Started getting camera working with alternate gravity axis
// 
// 29    8/18/98 12:03p Markd
// fixed camera fov stuff
// 
// 28    8/17/98 7:42p Markd
// Added SetOverlay command
// 
// 27    8/17/98 6:05p Markd
// Added nextcamera and overlay, made SetCamera send to all players
// 
// 26    8/17/98 4:33p Markd
// Added new camera stuff
// 
// 25    8/15/98 2:39p Markd
// fixed camera stuff
// 
// 24    8/14/98 8:18p Markd
// reworked camera class
// 
// 23    8/08/98 7:29p Aldie
// Added intermissions for deathmatch
// 
// 22    7/31/98 8:09p Jimdose
// Script commands now include flags to indicate cheats and console commands
// 
// 21    7/25/98 11:49p Markd
// fixed camera startup stuff
// 
// 20    7/22/98 5:28p Markd
// Redid camera stuff
// 
// 19    7/22/98 4:15p Markd
// Redid camera system
// 
// 18    7/20/98 2:38p Markd
// Fixed camera wackiness
// 
// 17    7/18/98 6:14p Markd
// Added optional watchEnt at the end of follow and orbit commands
// 
// 16    7/13/98 12:59p Markd
// Added fixedposition and nofixedposition
// 
// 15    7/12/98 9:43p Markd
// Accidentally commented out some camera following stuff
// 
// 14    7/12/98 8:43p Markd
// Fixed some camera smoothness issues
// 
// 13    7/11/98 6:31p Markd
// removed valid orientation, simplified code
// 
// 12    7/10/98 1:11p Markd
// Added some new camera commands
// 
// 11    7/08/98 12:41p Markd
// Added quaternion support to bsplines and also removed CameraPath
// 
// 10    7/02/98 9:48p Markd
// allowed camera to have orientation support from the bspline
// 
// 9     5/27/98 3:18a Jimdose
// cameras can now watch specific entities
// 
// 8     5/26/98 8:49p Jimdose
// Added yaw commands
// 
// 7     5/26/98 8:17p Jimdose
// added height script command
// fixed fov bug
// 
// 6     5/26/98 7:55p Jimdose
// added scripted cameras
// 
// 5     5/26/98 4:36a Jimdose
// Follow camera no longer clips into walls
// 
// 4     5/13/98 4:57p Jimdose
// now uses SafePtrs
// 
// 3     5/07/98 10:41p Jimdose
// fleshed out functionality a bit more
// 
// 2     5/05/98 2:37p Jimdose
// Created file
// 
// 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"

#define CAMERA_PATHFILE_VERSION 1

CameraManager CameraMan;

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;
	}

void Camera::Reset
   (
   Vector org,
   Vector ang
   )

   {
   setOrigin( org );
   setAngles( ang );
   InitializeState( currentstate );
   InitializeState( newstate );
   }

CLASS_DECLARATION( Camera, SecurityCamera, "func_securitycamera" );

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

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


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

  Camera Manager

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

Event EV_CameraManager_NewPath( "new", EV_CONSOLE | EV_CHEAT );
Event EV_CameraManager_SetPath( "setpath", EV_CONSOLE | EV_CHEAT );
Event EV_CameraManager_SetTargetName( "settargetname", EV_CONSOLE | EV_CHEAT );
Event EV_CameraManager_SetTarget( "settarget", EV_CONSOLE | EV_CHEAT );
Event EV_CameraManager_AddPoint( "add", EV_CONSOLE | EV_CHEAT );
Event EV_CameraManager_DeletePoint( "delete", EV_CONSOLE | EV_CHEAT );
Event EV_CameraManager_MovePlayer( "moveplayer", EV_CONSOLE | EV_CHEAT );
Event EV_CameraManager_ReplacePoint( "replace", EV_CONSOLE | EV_CHEAT );
Event EV_CameraManager_NextPoint( "next", EV_CONSOLE | EV_CHEAT );
Event EV_CameraManager_PreviousPoint( "prev", EV_CONSOLE | EV_CHEAT );
Event EV_CameraManager_ShowPath( "show" );
Event EV_CameraManager_HidePath( "hide" );
Event EV_CameraManager_PlayPath( "play", EV_CONSOLE | EV_CHEAT );
Event EV_CameraManager_LoopPath( "loop", EV_CONSOLE );
Event EV_CameraManager_StopPlayback( "stop" );
Event EV_CameraManager_Watch( "watch" );
Event EV_CameraManager_NoWatch( "nowatch" );
Event EV_CameraManager_Speed( "setspeed", EV_CONSOLE | EV_CHEAT );
Event EV_CameraManager_Save( "save", EV_CONSOLE | EV_CHEAT );
Event EV_CameraManager_Load( "load", EV_CONSOLE | EV_CHEAT );
Event EV_CameraManager_SaveMap( "savemap", EV_CONSOLE | EV_CHEAT );

CLASS_DECLARATION( Listener, CameraManager, NULL );

ResponseDef CameraManager::Responses[] =
	{
      { &EV_CameraManager_NewPath,         ( Response )&CameraManager::NewPath },
      { &EV_CameraManager_SetPath,         ( Response )&CameraManager::SetPath },
      { &EV_CameraManager_SetTargetName,   ( Response )&CameraManager::SetTargetName },
      { &EV_CameraManager_SetTarget,       ( Response )&CameraManager::SetTarget },
      { &EV_CameraManager_SetPath,         ( Response )&CameraManager::SetPath },
      { &EV_CameraManager_AddPoint,        ( Response )&CameraManager::AddPoint },
      { &EV_CameraManager_ReplacePoint,    ( Response )&CameraManager::ReplacePoint },
      { &EV_CameraManager_DeletePoint,     ( Response )&CameraManager::DeletePoint },
      { &EV_CameraManager_MovePlayer,      ( Response )&CameraManager::MovePlayer },
      { &EV_CameraManager_NextPoint,       ( Response )&CameraManager::NextPoint },
      { &EV_CameraManager_PreviousPoint,   ( Response )&CameraManager::PreviousPoint },
      { &EV_CameraManager_ShowPath,        ( Response )&CameraManager::ShowPath },
      { &EV_CameraManager_HidePath,        ( Response )&CameraManager::HidePath },
      { &EV_CameraManager_PlayPath,        ( Response )&CameraManager::PlayPath },
      { &EV_CameraManager_LoopPath,        ( Response )&CameraManager::LoopPath },
      { &EV_CameraManager_StopPlayback,    ( Response )&CameraManager::StopPlayback },
      { &EV_CameraManager_Watch,           ( Response )&CameraManager::Watch },
      { &EV_CameraManager_NoWatch,         ( Response )&CameraManager::NoWatch },
      { &EV_CameraManager_Speed,           ( Response )&CameraManager::Speed },
      { &EV_CameraManager_Save,            ( Response )&CameraManager::Save },
      { &EV_CameraManager_Load,            ( Response )&CameraManager::Load },
      { &EV_CameraManager_SaveMap,         ( Response )&CameraManager::SaveMap },
		{ NULL, NULL }
	};

Player *CameraManager_GetPlayer
   (
   void
   )

   {
   assert( g_edicts[ 1 ].entity && g_edicts[ 1 ].entity->isSubclassOf( Player ) );
   if ( !g_edicts[ 1 ].entity || !( g_edicts[ 1 ].entity->isSubclassOf( Player ) ) )
      {
      gi.printf( "No player found.\n" );
      return NULL;
      }

   return ( Player * )g_edicts[ 1 ].entity;
   }

CameraManager::CameraManager()
   {
   path = NULL;
   current = NULL;
   speed = 1;
   watch = 0;
   cam = NULL;
   }

void CameraManager::NewPath
   (
   Event *ev
   )

   {
   if ( path )
      {
      path = NULL;
      current = NULL;
      }

   ProcessEvent( EV_CameraManager_ShowPath );
   }

void CameraManager::SetPath
   (
   SplinePath *node
   )

   {
   path = node;
   current = node;
   }

void CameraManager::SetPath
   (
   Event *ev
   )

   {
   Entity *ent;

   if ( !ev->NumArgs() )
      {
      ev->Error( "Usage: cam setpath [pathname]" );
      return;
      }

   ent = ev->GetEntity( 1 );
   if ( !ent )
      {
      ev->Error( "Could not find path named '%s'.", ev->GetString( 1 ) );
      return;
      }

   if ( !ent->isSubclassOf( SplinePath ) )
      {
      ev->Error( "'%s' is not a camera path.", ev->GetString( 1 ) );
      return;
      }

   SetPath( ( SplinePath * )ent );
   }

void CameraManager::SetTargetName
   (
   Event *ev
   )

   {
	if ( ev->NumArgs() != 1 )
		{
      ev->Error( "Usage: cam targetname [name]" );
		return;
		}

   if ( !path )
      {
      ev->Error( "Camera path not set." );
      return;
      }

   path->SetTargetName( ev->GetString( 1 ) );
   }

void CameraManager::SetTarget
   (
   Event *ev
   )

   {
	if ( ev->NumArgs() != 1 )
		{
      ev->Error( "Usage: cam target [name]" );
		return;
		}

   if ( !current )
      {
      ev->Error( "Camera path not set." );
      return;
      }

   current->SetTarget( ev->GetString( 1 ) );
   }

void CameraManager::AddPoint
   (
   Event *ev
   )

   {
   Player *player;
   SplinePath *prev;
   SplinePath *next;
   Vector ang;
   Vector pos;

   player = CameraManager_GetPlayer();
   if ( player )
      {
      prev = current;
      if ( current )
         {
         next = current->GetNext();
         }
      else
         {
         next = NULL;
         }

      player->GetPlayerView( &pos, &ang );

      current = new SplinePath;
      current->setOrigin( pos );
      current->setAngles( ang );
      current->speed = speed;
      current->SetPrev( prev );
      current->SetNext( next );

      if ( !path )
         {
         path = current;
         }

      CancelEventsOfType( EV_CameraManager_ShowPath );
      ProcessEvent( EV_CameraManager_ShowPath );
      }
   }

void CameraManager::ReplacePoint
   (
   Event *ev
   )

   {
   Player *player;
   Vector ang;
   Vector pos;

   player = CameraManager_GetPlayer();
   if ( current && player )
      {
      player->GetPlayerView( &pos, &ang );

      current->setOrigin( pos );
      current->setAngles( ang );
      current->speed = speed;
      }
   }

void CameraManager::DeletePoint
   (
   Event *ev
   )

   {
   SplinePath *node;

   if ( current )
      {
      node = current->GetNext();
      if ( !node )
         {
         node = current->GetPrev();
         }

      if ( path == current )
         {
         path = current->GetNext();
         }

      delete current;
      current = node;
      }
   }

void CameraManager::MovePlayer
   (
   Event *ev
   )

   {
   Player *player;
   Vector pos;

   player = CameraManager_GetPlayer();
   if ( current && player )
      {
      player->GetPlayerView( &pos, NULL );

      player->setOrigin( current->origin - pos + player->origin );
      player->setAngles( current->angles );
      }
   }

void CameraManager::NextPoint
   (
   Event *ev
   )

   {
   SplinePath *next;

   if ( current )
      {
      next = current->GetNext();
      if ( next )
         {
         current = next;
         }
      }
   }

void CameraManager::PreviousPoint
   (
   Event *ev
   )

   {
   SplinePath *prev;

   if ( current )
      {
      prev = current->GetPrev();
      if ( prev )
         {
         current = prev;
         }
      }
   }

void CameraManager::ShowPath
   (
   Event *ev
   )

   {
   SplinePath *node;
   SplinePath *prev;
   Vector mins( -8, -8, -8 );
   Vector maxs( 8, 8, 8 );

   if ( ev->NumArgs() )
      {
      Entity *ent;

      ent = ev->GetEntity( 1 );
      if ( !ent )
         {
         ev->Error( "Could not find path named '%s'.", ev->GetString( 1 ) );
         return;
         }

      if ( !ent->isSubclassOf( SplinePath ) )
         {
         ev->Error( "'%s' is not a camera path.", ev->GetString( 1 ) );
         return;
         }

      SetPath( ( SplinePath * )ent );
      }

   prev = NULL;
   for( node = path; node != NULL; node = node->GetNext() )
      {
      if ( prev )
         {
         //G_LineStipple( 4, ( unsigned short )( 0xF0F0F0F0 >> ( 7 - ( level.framenum & 7 ) ) ) );
         G_DebugLine( prev->origin, node->origin, 1, 1, 1, 1 );
         //G_LineStipple( 1, 0xffff );
         }

      if ( current == node )
         {
         G_DrawDebugNumber( node->origin + Vector( 0, 0, 20 ), node->speed, 0.5, 0, 1, 0, 1 );
         G_DebugBBox( node->origin, mins, maxs, 1, 1, 0, 1 );
         }
      else
         {
         G_DebugBBox( node->origin, mins, maxs, 1, 0, 0, 1 );
         }

      //G_LineWidth( 3 );
      G_DebugLine( node->origin, node->origin + Vector( node->orientation[ 0 ] ) * 16, 1, 0, 0, 1 );
      G_DebugLine( node->origin, node->origin + Vector( node->orientation[ 1 ] ) * 16, 0, 1, 0, 1 );
      G_DebugLine( node->origin, node->origin + Vector( node->orientation[ 2 ] ) * 16, 0, 0, 1, 1 );
      //G_LineWidth( 1 );

      prev = node;
      }
   PostEvent( EV_CameraManager_ShowPath, FRAMETIME );
   }

void CameraManager::HidePath
   (
   Event *ev
   )

   {
   CancelEventsOfType( EV_CameraManager_ShowPath );
   }

void CameraManager::StopPlayback
   (
   Event *ev
   )

   {
   if ( cam )
      {
      cam->Stop();
      SetCamera( NULL );
      }
   }

void CameraManager::PlayPath
   (
   Event *ev
   )

   {
   if ( cam )
      {
      SetCamera( NULL );
      }

   if ( ev->NumArgs() )
      {
      Entity *ent;

      ent = ev->GetEntity( 1 );
      if ( !ent )
         {
         ev->Error( "Could not find path named '%s'.", ev->GetString( 1 ) );
         return;
         }

      if ( !ent->isSubclassOf( SplinePath ) )
         {
         ev->Error( "'%s' is not a camera path.", ev->GetString( 1 ) );
         return;
         }

      SetPath( ( SplinePath * )ent );
      }

   if ( path )
      {
      if ( !cam )
         {
         cam = new Camera;
         cam->SetTargetName( "_loadedcamera" );
         }
      
      cam->Reset( path->origin, path->angles );
      cam->FollowPath( path, false, NULL );
      SetCamera( cam );
      }
   }

void CameraManager::LoopPath
   (
   Event *ev
   )

   {
   if ( cam )
      {
      SetCamera( NULL );
      }

   if ( ev->NumArgs() )
      {
      Entity *ent;

      ent = ev->GetEntity( 1 );
      if ( !ent )
         {
         ev->Error( "Could not find path named '%s'.", ev->GetString( 1 ) );
         return;
         }

      if ( !ent->isSubclassOf( SplinePath ) )
         {
         ev->Error( "'%s' is not a camera path.", ev->GetString( 1 ) );
         return;
         }

      SetPath( ( SplinePath * )ent );
      }

   if ( path )
      {
      if ( !cam )
         {
         cam = new Camera;
         cam->SetTargetName( "_loadedcamera" );
         }

      cam->Reset( path->origin, path->angles );
      cam->FollowPath( path, true, NULL );
      SetCamera( cam );
      }
   }

void CameraManager::Watch
   (
   Event *ev
   )

   {
   if ( current )
      {
      current->SetWatch( ev->GetString( 1 ) );
      }
   }

void CameraManager::NoWatch
   (
   Event *ev
   )

   {
   if ( current )
      {
      current->NoWatch();
      }
   }

void CameraManager::Speed
   (
   Event *ev
   )

   {
   speed = ev->GetFloat( 1 );
   if ( current )
      {
      current->speed = speed;
      }
   }

void CameraManager::Save
   (
   Event *ev
   )

   {
   SplinePath *node;
	Archiver arc;
	str filename;
   int num;

	if ( ev->NumArgs() != 1 )
		{
      ev->Error( "Usage: cam save [filename]" );
		return;
		}

   num = 0;
   for( node = path; node != NULL; node = node->GetNext() )
      {
      num++;
      }

   if ( num == 0 )
      {
      ev->Error( "Can't save.  No points in path." );
		return;
      }

	filename = gi.GameDir();
	filename += "/cams/";
	filename += ev->GetString( 1 );
	filename += ".cam";

   if ( !path->targetname.length() )
      {
      path->SetTargetName( ev->GetString( 1 ) );
      gi.printf( "Targetname set to '%s'\n", path->targetname.c_str() );
      }

   gi.printf( "Saving camera path to '%s'...\n", filename.c_str() );

   arc.Create( filename );
	arc.WriteInteger( CAMERA_PATHFILE_VERSION );

   arc.WriteInteger( num );
   arc.WriteSafePointer( path );

   for( node = path; node != NULL; node = node->GetNext() )
      {
      arc.WriteObject( node );
      }

	arc.Close();

   gi.printf( "done.\n" );
   }

void CameraManager::Load
   (
   Event *ev
   )

   {
	Archiver arc;
	str		filename;
	int		version;
   int      i;
   int      num;

	if ( ev->NumArgs() != 1 )
		{
      ev->Error( "Usage: cam load [filename]" );
		return;
		}

	filename = "cams/";
	filename += ev->GetString( 1 );
	filename += ".cam";

   gi.printf( "Loading camera path from '%s'...\n", filename.c_str() );

	arc.Read( filename );
	version = arc.ReadInteger();
	if ( version == CAMERA_PATHFILE_VERSION )
		{
      arc.ReadInteger( &num );

		arc.ReadSafePointer( &path );

      for( i = 0; i < num; i++ )
         {
		   arc.ReadObject();
         }

      arc.Close();
      current = path;

      gi.printf( "done.\n" );
		}
	else
		{
      arc.Close();
      ev->Error( "Expecting version %d path file.  Camera path file is version %d.", CAMERA_PATHFILE_VERSION, version );
		}
   }


void CameraManager::SaveMap
   (
   Event *ev
   )

   {
   SplinePath *node;
   str        buf;
   char tempbuf[ 512 ];
	str filename;
   int num;
   int index;
   FILE * file;

	if ( ev->NumArgs() != 1 )
		{
      ev->Error( "Usage: cam savemap [filename]" );
		return;
		}

   num = 0;
   for( node = path; node != NULL; node = node->GetNext() )
      {
      num++;
      }

   if ( num == 0 )
      {
      ev->Error( "Can't save.  No points in path." );
		return;
      }

	filename = "cams/";
	filename += ev->GetString( 1 );
	filename += ".map";

   if ( !path->targetname.length() )
      {
      path->SetTargetName( ev->GetString( 1 ) );
      gi.printf( "Targetname set to '%s'\n", path->targetname.c_str() );
      }

   gi.printf( "Saving camera path to map '%s'...\n", filename.c_str() );

   buf = "";
   index = 0;
   for( node = path; node != NULL; node = node->GetNext() )
      {
      sprintf( tempbuf, "// pathnode %d\n", index );
      buf += str( tempbuf );
      buf += "{\n";
      sprintf( tempbuf, "\"classname\" \"info_splinepath\"\n" );
      buf += str( tempbuf );
      if ( index < ( num - 1 ) )
         {
         sprintf( tempbuf, "\"target\" \"camnode_%s_%d\"\n", ev->GetString( 1 ), index + 1 );
         buf += str( tempbuf );
         }
      if ( !index )
         {
         sprintf( tempbuf, "\"targetname\" \"%s\"\n", ev->GetString( 1 ) );
         }
      else
         {
         sprintf( tempbuf, "\"targetname\" \"camnode_%s_%d\"\n", ev->GetString( 1 ), index );
         }
      buf += str( tempbuf );
      sprintf( tempbuf, "\"origin\" \"%d %d %d\"\n", ( int )node->origin.x, ( int )node->origin.y, ( int )node->origin.z );
      buf += str( tempbuf );
      sprintf( tempbuf, "\"angles\" \"%d %d %d\"\n", ( int )anglemod( node->angles.x ), ( int )anglemod( node->angles.y ), ( int )anglemod( node->angles.z ) );
      buf += str( tempbuf );
      sprintf( tempbuf, "\"speed\" \"%.3f\"\n", node->speed );
      buf += str( tempbuf );
      buf += "}\n";
      index++;
      }

	gi.CreatePath( (char *)filename.c_str() );
	file = fopen( filename.c_str(), "wt" );
   fwrite( buf.c_str(), 1, buf.length() + 1, file );
   fclose( file );

   gi.printf( "done.\n" );
   }

