//-----------------------------------------------------------------------------
//
//  $Logfile:: /EF2/Code/DLLs/game/viewthing.cpp                            $
// $Revision:: 46                                                             $
//   $Author:: Singlis                                                        $
//     $Date:: 9/26/03 2:36p                                                  $
//
// 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:
// Actor code for the Viewthing.
//

#include "_pch_cpp.h"
#include "entity.h"
#include "viewthing.h"
#include "player.h"
#include "q_shared.h"
#include <qcommon/cm_public.h>

// These command vars are used with the viewset and viewlist commands
cvar_t         *g_vt_droptoggle;      // treated as a boolean.  If true, we drop newly spawned things
cvar_t         *g_vt_setFilename ;    // contains the filename of the current .lst file (if any)
cvar_t         *g_vt_modelIndex ;     // Index specifying which model in the .lst file we are viewing
cvar_t         *g_vt_numModels ;      // Number of total models in the .lst file
cvar_t         *g_vt_height ;         // Z Height off 0 that we position spawned things
cvar_t         *g_vt_scale ;          // Scale of spawned things
cvar_t         *g_vt_yaw ;            // Current yaw value for spawned things
cvar_t         *g_vt_roll ;           // Current roll value for spawned things
cvar_t         *g_vt_pitch ;          // Current pitch value for spawned things
cvar_t         *g_vt_xtrans ;         // Current x translation for current viewthing
cvar_t         *g_vt_ytrans ;         // Current y translation for current viewthing
cvar_t         *g_vt_ztrans ;         // Current z translation for current viewthing
cvar_t         *g_vt_makestatic ;     // Toggle for controlling if spawned objects are "static" (static lighting) or not.
cvar_t         *g_vt_usebox ;         // When shooting objects, objects pull away from wall by their bounding box.  Defaults true.
cvar_t         *g_vt_viewcount ;      // Holds number of spawned viewthings
cvar_t         *g_vt_viewnumber ;     // Holds position of current_viewthing amongst all spawned ones
cvar_t         *g_vt_viewrandomize ;  // If true (default is false) spawned things are affected by randomization settings (below)
cvar_t         *g_vt_viewsrandomize;  // If true randomize scale (default true)
cvar_t         *g_vt_viewzrandomize;  // If true randomize rotations about z axis (yaw) (default false)
cvar_t         *g_vt_viewyrandomize;  // If true randomize rotations about y axis (pitch) (default false)
cvar_t         *g_vt_viewxrandomize;  // If true randomize rotations about x axis (roll) (default false)
cvar_t         *g_vt_viewminrandomize;// Min value for randomizing scale (default is 50%)
cvar_t         *g_vt_viewmaxrandomize;// Max value for randomizing scale (default is 150%)
cvar_t         *g_vt_viewbellrandomize;// Set to true if you want scale randomizatins to be on a bell curve (default true)


Event EV_ViewThing_Think
(
	"viewthing_think",
	EV_CODEONLY,
	NULL,
	NULL,
	"Called every frame to process the view thing."
);
Event EV_ViewThing_ToggleAnimate
(
	"viewanimate",
	EV_CHEAT,
	NULL,
	NULL,
	"Cycle through the animations modes of the current viewthing\n"
	"No Animation\n"
	"Animation with no motion\n"
	"Animation with looping motion\n"
	"Animation with motion\n"
);
Event EV_ViewThing_SetModel
(
	"viewmodel",
	EV_CHEAT,
	"s",
	"viewthingModel",
	"Set the model of the current viewthing"
);
Event EV_ViewThing_NextFrame
(
	"viewnext",
	EV_CHEAT,
	NULL,
	NULL,
	"Advance to the next frame of animation of the current viewthing"
);
Event EV_ViewThing_PrevFrame
(
	"viewprev",
	EV_CHEAT,
	NULL,
	NULL,
	"Advance to the previous frame of animation of the current viewthing"
);
Event EV_ViewThing_NextAnim
(
	"viewnextanim",
	EV_CHEAT,
	NULL,
	NULL,
	"Advance to the next animation of the current viewthing"
);
Event EV_ViewThing_PrevAnim
(
	"viewprevanim",
	EV_CHEAT,
	NULL,
	NULL,
	"Advance to the previous animation of the current viewthing"
);
Event EV_ViewThing_SetFrame
(
	"viewsetframe",
	EV_CHEAT,
	NULL,
	NULL,
	"Set the animation to a specific frame number."
);
Event EV_ViewThing_ScaleUp
(
	"viewscaleup",
	EV_CHEAT,
	NULL,
	NULL,
	"Increase the scale of the current viewthing"
);
Event EV_ViewThing_ScaleDown
(
	"viewscaledown",
	EV_CHEAT,
	NULL,
	NULL,
	"Decrease the scale of the current viewthing"
);
Event EV_ViewThing_SetScale
(
	"viewscale",
	EV_CHEAT,
	"f",
	"scale",
	"Set the scale of the current viewthing"
);
Event EV_ViewThing_SetYaw
(
	"viewyaw",
	EV_CHEAT,
	"f",
	"yaw",
	"Set the yaw of the current viewthing"
);
Event EV_ViewThing_SetPitch
(
	"viewpitch",
	EV_CHEAT,
	"f",
	"pitch",
	"Set the pitch of the current viewthing"
);
Event EV_ViewThing_SetRoll
(
	"viewroll",
	EV_CHEAT,
	"f",
	"roll",
	"Set the roll of the current viewthing"
);
Event EV_ViewThing_SetAngles
(
	"viewangles",
	EV_CHEAT,
	"f[0,360]f[0,360]f[0,360]",
	"pitch yaw roll",
	"Set the angles of the current viewthing"
);
Event EV_ViewThing_TranslateX
(
	"viewxtranslate",
	EV_CHEAT,
	"f",
	"xtrans",
	"Translate current viewthing in x direction"
);
Event EV_ViewThing_TranslateY
(
	"viewytranslate",
	EV_CHEAT,
	"f",
	"ytrans",
	"Translate current viewthing in y direction"
);
Event EV_ViewThing_TranslateZ
(
	"viewztranslate",
	EV_CHEAT,
	"f",
	"ztrans",
	"Translate current viewthing in z direction"
);
Event EV_ViewThing_Pull
(
	"viewthingpull",
	EV_CHEAT,
	NULL,
	NULL,
	"Pulls the current viewthing to the camera"
);
Event EV_ViewThing_Spawn
(
	"viewspawn",
	EV_CHEAT,
	"s",
	"model",
	"Create a viewthing with the specified model"
);
Event EV_ViewThing_SpawnFromTS
(
	"viewspawnfromts",
	EV_CHEAT,
	"sS",
	"reloadmodel spawnmodel",
	"Create a viewthing from the model specified by the ToolServer"
);
Event EV_ViewThing_Next
(
	"viewthingnext",
	EV_CHEAT,
	NULL,
	NULL,
	"Change the active viewthing to the next viewthing"
);
Event EV_ViewThing_Prev
(
	"viewthingprev",
	EV_CHEAT,
	NULL,
	NULL,
	"Change the active viewthing to the previous viewthing"
);
Event EV_ViewThing_Attach
(
	"viewattach",
	EV_CHEAT,
	"ss",
	"tagname model",
	"Attach a model the the specified tagname"
);
Event EV_ViewThing_Detach
(
	"viewdetach",
	EV_CHEAT,
	NULL,
	NULL,
	"Detach the current viewthing from its parent"
);
Event EV_ViewThing_DetachAll
(
	"viewdetachall",
	EV_CHEAT,
	NULL,
	NULL,
	"Detach all the models attached to the current viewthing"
);
Event EV_ViewThing_Delete
(
	"viewdelete",
	EV_CHEAT,
	NULL,
	NULL,
	"Delete the current viewthing"
);
Event EV_ViewThing_SetStatic
(
	"viewsetstatic",
	EV_CHEAT,
	"i",
	"boolean",
	"Makes the current viewthing a static (not dynamically lit) object"
);
Event EV_ViewThing_SetOrigin
(
	"vieworigin",
	EV_CHEAT,
	"fff",
	"x y z",
	"Set the origin of the current viewthing"
);
Event EV_ViewThing_DeleteAll
(
	"viewdeleteall",
	EV_CHEAT,
	NULL,
	NULL,
	"Delete all viewthings"
);
Event EV_ViewThing_LastFrame
(
	"viewlastframe",
	EV_CODEONLY,
	NULL,
	NULL,
	"Called when the view things last animation frame is displayed."
);
Event EV_ViewThing_SaveOffSurfaces
(
	"viewsavesurfaces",
	EV_CODEONLY,
	NULL,
	NULL,
	"Called after the model is spawned to save off the models original surfaces."
);
Event EV_ViewThing_PrintTime
(
	"_viewthing_printtime",
	EV_CODEONLY,
	NULL,
	NULL,
	"Prints out the current level.time."
);
Event EV_ViewThing_SetAnim
(
	"viewsetanim",
	EV_CHEAT,
	"f",
	"animNum",
	"Set the animation absolutely based off a floating point value"
);
Event EV_ViewThing_NextMorph
(
	"viewnextmorph",
	EV_CHEAT,
	NULL,
	NULL,
	"Goes to the next morph for this model"
);
Event EV_ViewThing_PrevMorph
(
	"viewprevmorph",
	EV_CHEAT,
	NULL,
	NULL,
	"Goes to the previous morph for this model"
);
Event EV_ViewThing_Morph
(
	"viewmorph",
	EV_CHEAT,
	NULL,
	NULL,
	"Morphs the current morph"
);
Event EV_ViewThing_Unmorph
(
	"viewunmorph",
	EV_CHEAT,
	NULL,
	NULL,
	"Unmorphs the current morph"
);
Event EV_ViewSet_Load
(
	"viewsetload",
	EV_CHEAT,
	"s",
	"filename",
	"Loads the specified viewset"
);
Event EV_ViewSet_Save
(
	"viewsetsave",
	EV_CHEAT,
	NULL,
	NULL,
	"Saves names and positions of currently spawned things data to disk"
);
Event EV_ViewThing_Copy
(
	"viewcopy",
	EV_CHEAT,
	NULL,
	NULL,
	"Copies the current viewthing"
);
Event EV_ViewThing_Shoot
(
	"viewshoot",
	EV_CHEAT,
	NULL,
	NULL,
	"Shoots current viewthing ahead and sticks it on the wall"
);
Event EV_ViewList_List
(
	"viewlistlist",
	EV_CHEAT,
	NULL,
	NULL,
	"List all TIKIs in the current .lst set"
);
Event EV_ViewList_NextInSet
(
	"viewlistnext",
	EV_CHEAT,
	NULL,
	NULL,
	"Delete current viewthing and load next view thing in view list"
);
Event EV_ViewList_PrevInSet
(
	"viewlistprev",
	EV_CHEAT,
	NULL,
	NULL,
	"Delete current viewthing and load prev view thing in view list"
);
Event EV_ViewList_LiftModel
(
	"viewlistliftmodel",
	EV_CHEAT,
	"f",
	"z",
	"Raise the origin (z) by specified amount"
);
Event EV_ViewList_ToggleDrop
(
	"viewlisttoggledrop",
	EV_CHEAT,
	NULL,
	NULL,
	"Toggle whether objects are dropped upon spawning"
);
Event EV_ViewList_Jump
(
	"viewlistjump",
	EV_CHEAT,
	"i",
	"modelindex",
	"Jumps to a specific model in a view set (.lst file) of models"
);
Event EV_ViewThing_ResetOrigin
(
	"viewresetorigin",
	EV_CHEAT,
	NULL,
	NULL,
	"Sets the origin of current viewspawn to its current origin"
);
Event EV_ViewThing_XTrans
(
	"viewtransx",
	EV_CHEAT,
	"i",
	"offsetamount",
	"translate current viewspawn in x direction by specified amount"
);
Event EV_ViewThing_YTrans
(
	"viewtransy",
	EV_CHEAT,
	"i",
	"offsetamount",
	"translate current viewspawn in y direction by specified amount"
);
Event EV_ViewThing_ZTrans
(
	"viewtransz",
	EV_CHEAT,
	"i",
	"offsetamount",
	"translate current viewspawn in z direction by specified amount"
);
Event EV_ViewThing_Offset
(
	"viewoffset",
	EV_CHEAT,
	"fff",
	"x y z",
	"Offsets the origin of the current viewthing"
);
Event EV_ViewThing_ChangeRoll
(
	"viewchangeroll",
	EV_CHEAT,
	"i",
	"offsetamount",
	"rotate current viewspawn around x axis by specified amount"
);
Event EV_ViewThing_ChangePitch
(
	"viewchangepitch",
	EV_CHEAT,
	"i",
	"offsetamount",
	"rotate current viewspawn around y axis by specified amount"
);
Event EV_ViewThing_ChangeYaw
(
	"viewchangeyaw",
	EV_CHEAT,
	"i",
	"offsetamount",
	"rotate current viewspawn around z axis by specified amount"
);
Event EV_ViewThing_Flash
(
	"viewflash",
	EV_CHEAT,
	NULL,
	NULL,
	"Flashes the currently select viewthing"
);

CLASS_DECLARATION( Listener, ViewMaster, NULL )
{
	{ &EV_ViewThing_Spawn,				&ViewMaster::Spawn },
	{ &EV_ViewThing_SpawnFromTS,		&ViewMaster::SpawnFromTS },
	{ &EV_ViewThing_Next,				&ViewMaster::Next },
	{ &EV_ViewThing_Prev,				&ViewMaster::Prev },
	{ &EV_ViewThing_SetModel,			&ViewMaster::SetModelEvent },
	{ &EV_ViewThing_DeleteAll,			&ViewMaster::DeleteAll },
	{ &EV_ViewThing_ToggleAnimate,		&ViewMaster::PassEvent },
	{ &EV_ViewThing_NextFrame,			&ViewMaster::PassEvent },
	{ &EV_ViewThing_PrevFrame,			&ViewMaster::PassEvent },
	{ &EV_ViewThing_NextAnim,			&ViewMaster::PassEvent },
	{ &EV_ViewThing_PrevAnim,			&ViewMaster::PassEvent },
	{ &EV_ViewThing_ScaleUp,			&ViewMaster::PassEvent },
	{ &EV_ViewThing_ScaleDown,			&ViewMaster::PassEvent },
	{ &EV_ViewThing_SetScale,			&ViewMaster::PassEvent },
	{ &EV_ViewThing_SetYaw,				&ViewMaster::PassEvent },
	{ &EV_ViewThing_SetPitch,			&ViewMaster::PassEvent },
	{ &EV_ViewThing_SetRoll,			&ViewMaster::PassEvent },
	{ &EV_ViewThing_SetAngles,			&ViewMaster::PassEvent },
	{ &EV_ViewThing_Attach,				&ViewMaster::PassEvent },
	{ &EV_ViewThing_Detach,				&ViewMaster::PassEvent },
	{ &EV_ViewThing_DetachAll,			&ViewMaster::PassEvent },
	{ &EV_ViewThing_SetStatic,          &ViewMaster::PassEvent },
	{ &EV_ViewThing_SetOrigin,			&ViewMaster::PassEvent },
	{ &EV_ViewThing_SetAnim,			&ViewMaster::PassEvent },
	{ &EV_ViewThing_NextMorph,			&ViewMaster::PassEvent },
	{ &EV_ViewThing_PrevMorph,			&ViewMaster::PassEvent },
	{ &EV_ViewThing_Morph,				&ViewMaster::PassEvent },
	{ &EV_ViewThing_Unmorph,			&ViewMaster::PassEvent },
	{ &EV_ViewThing_ChangeRoll,         &ViewMaster::PassEvent },
	{ &EV_ViewThing_ChangePitch,        &ViewMaster::PassEvent },
	{ &EV_ViewThing_ChangeYaw,          &ViewMaster::PassEvent },
	{ &EV_ViewThing_Flash,              &ViewMaster::PassEvent },
	{ &EV_ViewThing_SetFrame,           &ViewMaster::PassEvent },
	{ &EV_ViewSet_Load,                 &ViewMaster::LoadSet   },
	{ &EV_ViewSet_Save,                 &ViewMaster::Save      },
	{ &EV_ViewThing_Copy,               &ViewMaster::Copy      },
	{ &EV_ViewThing_Shoot,              &ViewMaster::Shoot     },
	{ &EV_ViewThing_Delete,             &ViewMaster::Delete    },
	{ &EV_ViewThing_TranslateX,         &ViewMaster::SetXTranslation },
	{ &EV_ViewThing_TranslateY,         &ViewMaster::SetYTranslation },
	{ &EV_ViewThing_TranslateZ,         &ViewMaster::SetZTranslation },
	{ &EV_ViewList_PrevInSet,           &ViewMaster::PrevModelInSet },
	{ &EV_ViewList_NextInSet,           &ViewMaster::NextModelInSet },
	{ &EV_ViewList_LiftModel,           &ViewMaster::SetZTranslation },
	{ &EV_ViewList_ToggleDrop,          &ViewMaster::ToggleDrop },
	{ &EV_ViewList_Jump,                &ViewMaster::JumpToModel },
	{ &EV_ViewThing_Pull,               &ViewMaster::PullToCamera },
	{ &EV_ViewThing_ResetOrigin,        &ViewMaster::ResetOrigin },
	{ &EV_ViewThing_XTrans,             &ViewMaster::XTranslate },
	{ &EV_ViewThing_YTrans,             &ViewMaster::YTranslate },
	{ &EV_ViewThing_ZTrans,             &ViewMaster::ZTranslate },
	{ &EV_ViewThing_Offset,             &ViewMaster::Offset },
	{ NULL, NULL }
};

ViewMaster Viewmodel;

ViewMaster::ViewMaster()
{
	current_viewthing       = NULL;
	_spawnAtLastItemsOrigin = false ;
	_currentSetIdx          = 0 ;
	_numberOfSets           = 0 ;
	_numberOfModelsInSet    = 0 ;
}

ViewMaster::~ViewMaster()
{
	_setNamesArray.FreeObjectList();
	_modelNamesArray.FreeObjectList();
}

void ViewMaster::Init( void )
{
	gi.AddCommand( "viewanimate" );
	gi.AddCommand( "viewmodel" );
	gi.AddCommand( "viewnext" );
	gi.AddCommand( "viewprev" );
	gi.AddCommand( "viewnextanim" );
	gi.AddCommand( "viewprevanim" );
	gi.AddCommand( "viewscaleup" );
	gi.AddCommand( "viewscaledown" );
	gi.AddCommand( "viewscale" );
	gi.AddCommand( "viewyaw" );
	gi.AddCommand( "viewpitch" );
	gi.AddCommand( "viewroll" );
	gi.AddCommand( "viewangles" );
	gi.AddCommand( "viewxtranslate" );
	gi.AddCommand( "viewytranslate" );
	gi.AddCommand( "viewztranslate" );
	gi.AddCommand( "viewoffset" );
	gi.AddCommand( "viewspawn" );
	gi.AddCommand( "viewspawnfromts");
	gi.AddCommand( "viewthingnext" );
	gi.AddCommand( "viewthingprev" );
	gi.AddCommand( "viewthingpull" );
	gi.AddCommand( "viewattach" );
	gi.AddCommand( "viewdetach" );
	gi.AddCommand( "viewdetachall" );
	gi.AddCommand( "viewdelete" );
	gi.AddCommand( "viewsetstatic"  );
	gi.AddCommand( "vieworiginlift" );
	gi.AddCommand( "vieworigin" );
	gi.AddCommand( "viewdeleteall" );
	gi.AddCommand( "viewsetanim"   );
	gi.AddCommand( "viewsetload"   );
	gi.AddCommand( "viewsetnext"   );
	gi.AddCommand( "viewsetprev"   );
	gi.AddCommand( "viewsetsave"   );
	gi.AddCommand( "viewcopy"      );
	gi.AddCommand( "viewlistlist"  );
	gi.AddCommand( "viewlistnext"  );
	gi.AddCommand( "viewlistprev"  );
	gi.AddCommand( "viewlistliftmodel" );
	gi.AddCommand( "viewlisttoggledrop" );
	gi.AddCommand( "viewlistjump" );
	gi.AddCommand( "viewresetorigin" );
	gi.AddCommand( "viewtransx" );
	gi.AddCommand( "viewtransy" );
	gi.AddCommand( "viewtransz" );
	gi.AddCommand( "viewflash" );
	gi.AddCommand( "viewsetframe" );
	
	g_vt_droptoggle         = gi.cvar( "g_vt_droptoggle",       "1",    0 );
	g_vt_setFilename        = gi.cvar( "g_vt_setFilename",      "",     0 );
	g_vt_modelIndex         = gi.cvar( "g_vt_modelIndex",       "0",    0 );
	g_vt_height             = gi.cvar( "g_vt_height",           "0",    0 );
	g_vt_makestatic         = gi.cvar( "g_vt_makestatic",       "1",    0 );
	g_vt_scale              = gi.cvar( "g_vt_scale",            "1.0",  0 );
	g_vt_numModels          = gi.cvar( "g_vt_numModels",        "0",    0 );
	g_vt_yaw                = gi.cvar( "g_vt_yaw",              "0.0",  0 );
	g_vt_pitch              = gi.cvar( "g_vt_pitch",            "0.0",  0 );
	g_vt_roll               = gi.cvar( "g_vt_roll",             "0.0",  0 );
	g_vt_xtrans             = gi.cvar( "g_vt_xtrans",           "0.0",  0 );
	g_vt_ytrans             = gi.cvar( "g_vt_ytrans",           "0.0",  0 );
	g_vt_ztrans             = gi.cvar( "g_vt_ztrans",           "0.0",  0 );  
	g_vt_usebox             = gi.cvar( "g_vt_usebox",           "1",    0 );
	g_vt_viewnumber         = gi.cvar( "g_vt_viewnumber",       "0",    0 );
	g_vt_viewcount          = gi.cvar( "g_vt_viewcount",        "0",    0 );
	g_vt_viewrandomize      = gi.cvar( "g_vt_viewrandomize",    "0",    0 );
	g_vt_viewsrandomize     = gi.cvar( "g_vt_viewsrandomize",   "1",    0 );
	g_vt_viewzrandomize     = gi.cvar( "g_vt_viewzrandomize",   "1",    0 );
	g_vt_viewyrandomize     = gi.cvar( "g_vt_viewyrandomize",   "0",    0 );
	g_vt_viewxrandomize     = gi.cvar( "g_vt_viewxrandomize",   "0",    0 );
	g_vt_viewminrandomize   = gi.cvar( "g_vt_viewminrandomize", "25",   0 );
	g_vt_viewmaxrandomize   = gi.cvar( "g_vt_viewmaxrandomize", "175",  0 );
	g_vt_viewbellrandomize  = gi.cvar( "g_vt_viewminrandomize", "1",    0 );
}

void ViewMaster::Save( Event *ev )
{
	// saves currently spawned things to disk
	str         buf;
	str         filename;
	int         count = 1 ;
	
	// get the name of the sound file from the world
	filename = "maps/";
	filename += level.mapname;
	for(int i = filename.length() - 1; i >= 0; i-- )
	{
		if ( filename[ i ] == '.' )
		{
			filename[ i ] = 0;
			break;
		}
	}
	
	filename += "_ents.map";
	gi.cvar_set("g_vt_savefile", filename.c_str());
	gi.Printf( "Saving spawned things to '%s'...\n", filename.c_str() );
	
	buf = "";
	buf += va( "//\n" );
	buf += va( "// Viewspawned entities file \"%s\".\n", filename.c_str());
	buf += va( "//\n" );
	
	Viewthing *viewthingPtr = 0 ;
	Entity    *entityPtr    = 0 ;
	entityPtr = G_FindClass( entityPtr, "viewthing" );
	
	while (entityPtr != 0)
	{
		viewthingPtr = (Viewthing*)entityPtr;
		
		str   modelName     = viewthingPtr->model ;
		char* strPtr        = (char*)modelName.c_str();
		char* endPtr        = strPtr + (strlen(strPtr) - 1) ;
		bool  found         = false ;
		
		for (int idx=strlen(strPtr); idx > 0; idx++)
		{
			if (*endPtr == '/')
			{
				endPtr++ ;
				found = true ;
				break ;
			}
			endPtr-- ;
		}			
		
		if (found)
		{
			strPtr = endPtr ;
		}
		
		buf += va( "// Entity %d\n", count);
		buf += va( "{\n");
		buf += va( "\"make_static\" \"%d\"\n",				viewthingPtr->_static);
		buf += va( "\"classname\"   \"FROM_TIKI\"\n" );
		buf += va( "\"model\"       \"%s\"\n",				strPtr);			
		buf += va( "\"scale\"       \"%f\"\n",				viewthingPtr->edict->s.scale);
		buf += va( "\"origin\"      \"%.2f %.2f %.2f\"\n",	viewthingPtr->origin.x, viewthingPtr->origin.y, viewthingPtr->origin.z );
		buf += va( "\"angles\"      \"%.2f %.2f %.2f\"\n",	viewthingPtr->angles.x, viewthingPtr->angles.y, viewthingPtr->angles.z );
		buf += va( "\"spawnflags\"  \"1\"\n");
		buf += va( "}\n" );
		
		entityPtr = G_FindClass( entityPtr, "viewthing");
		count++ ;
	}
	gi.FS_WriteFile( filename.c_str(), buf.c_str(), buf.length() + 1 );
	gi.Printf( "Done.\n" );		
}

void ViewMaster::LoadSet( Event *ev )
{
	// If we have stuff loaded, delete it all
	if (_numberOfModelsInSet || _numberOfSets)
	{
		gi.cvar_set( "viewsetname",       va( " ") );
		gi.cvar_set( "viewlistmodelname", va( " ") );
		gi.cvar_set( "g_vt_modelIndex",   "0");
		gi.cvar_set( "g_vt_setFilename",  "");
		
		_currentSetIdx       = 0 ;
		_numberOfSets        = 0 ;
		_numberOfModelsInSet = 0 ;
		_setNamesArray.FreeObjectList();
		_modelNamesArray.FreeObjectList();
	}
	
	str filename = ev->GetString(1);
	_setNamesArray.AddObject(filename);
	_numberOfSets++ ;
	
	// If we loaded any tiki filenames, display the first one
	if (_numberOfSets)
	{
		_currentSetIdx = 1 ;
		//str setFilename = _setNamesArray.ObjectAt(_currentSetIdx);
		LoadModelsInSet(_setNamesArray.ObjectAt(_currentSetIdx));
	}
}

		
void ViewMaster::LoadModelsInSet( const str &setFileName )
{	
	char  *buffer;
	char  *buf;
	char   com_token[MAX_STRING_CHARS];
	
	if (_numberOfModelsInSet)
	{
		gi.cvar_set( "viewlistmodelname", va( " ") );
		gi.cvar_set( "g_vt_modelIndex",   "0");
		_modelNamesArray.FreeObjectList();
		_numberOfModelsInSet = 0 ;
	}
	
	// Read the viewlist.txt file.  This file contains newline seperated list of .tik models
	if ( gi.FS_ReadFile( setFileName, ( void ** )&buf, true ) != -1 )
	{
		buffer = buf;
		while ( 1 )
		{
			// Pull out a token
			strcpy( com_token, COM_ParseExt( &buffer, true ) );
			
			// Quit when no more tokens
			if (!com_token[0])
				break;
			
			// Check for single comment lines
			if ((com_token[0] != '#')) {  // # is a single line comment
				const char *relative_pathname = NULL;
				
				str modelName  = "models/" ;
				
				relative_pathname = strstr( com_token, "models/" );
				
				if ( !relative_pathname )
					relative_pathname = strstr( com_token, "models\\" );
				
				if ( relative_pathname )
				{
					relative_pathname += strlen( "models/" );
					modelName += relative_pathname;
				}
				else
				{
					modelName += com_token;
				}
				
				_modelNamesArray.AddObject(modelName);
				_numberOfModelsInSet++ ;
			}
			
			// Read in the rest of the line
			while( 1 )
			{
				strcpy( com_token, COM_ParseExt( &buffer, false ) );
				if (!com_token[0])
					break;
			}
		}
		gi.FS_FreeFile( buf );
		
		// If we loaded any tiki filenames, display the first one
		if (_numberOfModelsInSet)
		{
			gi.cvar_set( "g_vt_modelIndex",  "1");
			DisplayCurrentModelInSet(false) ;
		}
		
		str setName = _setNamesArray.ObjectAt(_currentSetIdx);
		gi.cvar_set( "viewsetname",    va( "%s", setName.c_str()) );
		gi.cvar_set( "g_vt_numModels", va( "%d", _numberOfModelsInSet) );
	}
}	

void ViewMaster::DisplayCurrentModelInSet( bool spawnAtLastOrigin )
{
	if ( (g_vt_modelIndex->integer <= 0) || (g_vt_modelIndex->integer > _numberOfModelsInSet) )
	{
		gi.cvar_set( "viewlistmodelname", " " );
		gi.Printf( " No models in set or position out of range\n");
		return ; 
	}
	
	
	// Get the string name of the view item
	str modelName = _modelNamesArray.ObjectAt(g_vt_modelIndex->integer);
	
	// Create a new spawn event with item name to send to Spawn function
	Event* spawnEvent = new Event( EV_ViewThing_Spawn);
	spawnEvent->AddString(modelName);
	_spawnAtLastItemsOrigin = spawnAtLastOrigin ;
	Spawn(spawnEvent);
	_spawnAtLastItemsOrigin = false ;
	delete spawnEvent ;
	spawnEvent = 0 ;
	
	gi.cvar_set( "viewlistnumitems",  va( "%d", _numberOfModelsInSet ) );
	gi.cvar_set( "viewlistmodelname", modelName.c_str() );
	gi.cvar_set( "g_vt_scale",  "1.0");
	gi.cvar_set( "g_vt_height", "0"  );
}

//================================================================
// Name:        Copy
// Class:       ViewMaster
//              
// Description: Event function to spawn a duplicate of the current
//              viewthing at the spot occupied by the player.
//         
//              The object should be a true copy, including rotation
//              and scaling.  But currently isn't.  FIXME
//     
// Parameters:  Event* -- Not used
//              
// Returns:     None
//              
//================================================================
void ViewMaster::Copy( Event * )
{
	DisplayCurrentModelInSet(false);
	_updateViewthingCounts();
}

//================================================================
// Name:        Shoot
// Class:       ViewMaster
//              
// Description: Event function to spawn a duplicate of the current
//              viewthing at the spot aimed by the player.  The new
//              viewthing is not copied in scale or rotation.
//              
// Parameters:  Event* -- Not used
//              
// Returns:     None
//              
//================================================================
void ViewMaster::Shoot( Event *ev )
{
	int idx = g_vt_modelIndex->integer ;
	if ( (idx <= 0) || (idx > _modelNamesArray.NumObjects()) ) return ;
	
	Vector  forward ;
	Vector  right ;
	Vector  up ;
	Vector  view ;
	Vector  start;
	Vector	end;
	Vector  mins = vec_zero ;
	Vector  maxs = vec_zero ;
	trace_t	trace;
	
	// Check if we have a client
	Player* player = (Player*)g_entities[ 0 ].entity;
	if ( !player ) return;
	
	Viewthing* viewthing = 0 ;
	if (current_viewthing && g_vt_usebox->integer)
	{
		viewthing = (Viewthing*) ( (Entity*)current_viewthing);
		mins = viewthing->mins ;
		maxs = viewthing->maxs ;
	}
	
	view    = player->GetVAngles();
	AngleVectors( view, forward, right, up );
	start    = player->origin ;
	start.z += player->viewheight ;
	end      = start + ( forward * 10000.0f ) ;
	
	trace = G_FullTrace( start, mins, maxs, end, player, MASK_SHOT, false, "ViewMaster::shoot" );
	if ( ( trace.fraction == 1.0f ) || trace.allsolid || !trace.ent )
	{
		return ;
	}
	
	if ( g_showbullettrace->integer )
	{
		G_DebugLine( start, end, 1.0f, 1.0f, 1.0f, 1.0f );
	}
	
	Vector pos = Vector( trace.endpos ) + ( Vector( trace.plane.normal ) * 0.2f );
	
	str    modelName(_modelNamesArray.ObjectAt(g_vt_modelIndex->integer));
	spawnAtPosition(modelName, pos);
	
	if (current_viewthing)
	{
		Event* yawEvent = new Event(EV_ViewThing_SetYaw);
		viewthing = (Viewthing*) ( (Entity*)current_viewthing);
		Vector n(trace.plane.normal);
		float yaw = n.toYaw();
		yawEvent->AddFloat(yaw);
		viewthing->SetYawEvent(yawEvent);
		delete yawEvent ;
		yawEvent = 0 ;
		
		if (g_vt_viewrandomize->integer)
		{
			_randomizeViewthing(viewthing);
		}
	}
}

//================================================================
// Name:        NextModelInSet
// Class:       ViewMaster
//              
// Description: Event function to switch current_viewthing to the
//              next model in the set (if any).  This will switch
//              the current tiki model to be the next tiki model in
//              the loaded .lst file.  It does not change the number
//              of total viewthings in the world.  Wraps when at
//              end of list.
//              
// Parameters:  Event* -- Not used
//              
// Returns:     None
//              
//================================================================
void ViewMaster::NextModelInSet( Event * )
{
	if (!_numberOfModelsInSet)
	{
		gi.Printf(" No current set\n");
		return ;
	}
	
	// Increment on to next model
	int nextIndex = ( g_vt_modelIndex->integer >= _numberOfModelsInSet) ? 1 : (g_vt_modelIndex->integer + 1 );
	gi.cvar_set( "g_vt_modelIndex",  va( "%d", nextIndex ) );
	DeleteCurrentViewthing();
	DisplayCurrentModelInSet();
}

//================================================================
// Name:        PrevModelInSet
// Class:       ViewMaster
//              
// Description: Event function to switch current_viewthing to the
//              prev model in the set (if any).  This will switch
//              the current tiki model to be the prev tiki model in
//              the loaded .lst file.  It does not change the number
//              of total viewthings in the world.  Wraps when at 
//              start of list.
//              
// Parameters:  Event* -- Not used
//              
// Returns:     None
//              
//================================================================
void ViewMaster::PrevModelInSet( Event *ev )
{
	if (!_numberOfModelsInSet)
	{
		gi.Printf(" No models in current set\n");
		return ;
	}
	
	// Increment on to next model
	int nextIndex = ( g_vt_modelIndex->integer <= 1) ? _numberOfModelsInSet : ( g_vt_modelIndex->integer - 1 );
	gi.cvar_set( "g_vt_modelIndex",  va( "%d", nextIndex ) );
	DeleteCurrentViewthing();
	DisplayCurrentModelInSet() ;
}

//================================================================
// Name:        JumpToModel
// Class:       ViewMaster
//              
// Description: Explicity function to jump to the tiki specified
//              by number out of the total list of tiki's in the
//              load .lst file.  This enables random access thru
//              the .lst instead of one at a time.
//              
// Parameters:  Event* -- first argument is position to jump to
//              
// Returns:     None
//              
//================================================================
void ViewMaster::JumpToModel( Event *ev )
{
	int nextIndex = ev->GetInteger(1);
	if ((nextIndex < 1) || (nextIndex > _numberOfModelsInSet))
	{
		gi.Printf(" Illegal index for model in viewset list\n");
		return ;
	}
	
	gi.cvar_set( "g_vt_modelIndex",  va( "%d", nextIndex ) );
	DeleteCurrentViewthing();
	DisplayCurrentModelInSet();
}

void ViewMaster::SetXTranslation( Event *ev )
{
	if (!current_viewthing) return ;
	
	Viewthing *viewthing = ( Viewthing * )( ( Entity * )current_viewthing );
	Event *originEvent   = new Event(EV_ViewThing_SetOrigin);
	
	float y = viewthing->origin.y ;
	float z = viewthing->origin.z ;
	originEvent->AddFloat(_lastBaseOrigin.x + ev->GetFloat(1));
	originEvent->AddFloat(y);
	originEvent->AddFloat(z);
	viewthing->ChangeOrigin(originEvent);
	delete originEvent ;
	originEvent = 0 ;
	
	gi.cvar_set( "g_vt_xtrans", va("%0.2f", ev->GetFloat(1)) );
}

void ViewMaster::XTranslate( Event *ev )
{
	if (!current_viewthing) return ;
	
	Viewthing *viewthing = ( Viewthing * )( ( Entity * )current_viewthing );
	Event *originEvent   = new Event(EV_ViewThing_SetOrigin);
	
	originEvent->AddFloat(viewthing->origin.x + ev->GetFloat(1));
	originEvent->AddFloat(viewthing->origin.y);
	originEvent->AddFloat(viewthing->origin.z);
	
	viewthing->ChangeOrigin(originEvent);
	delete originEvent ;
	originEvent = 0 ;
	
	_resetOrigin();
}

void ViewMaster::SetYTranslation( Event *ev )
{
	if (!current_viewthing) return ;
	
	Viewthing *viewthing = ( Viewthing * )( ( Entity * )current_viewthing );
	Event *originEvent = new Event(EV_ViewThing_SetOrigin);
	
	float x = viewthing->origin.x ;
	float z = viewthing->origin.z ;
	originEvent->AddFloat(x);
	originEvent->AddFloat(_lastBaseOrigin.y + ev->GetFloat(1));
	originEvent->AddFloat(z);
	viewthing->ChangeOrigin(originEvent);
	delete originEvent ;
	originEvent = 0 ;
	
	gi.cvar_set( "g_vt_ytrans", va("%0.2f", ev->GetFloat(1)) );
}

void ViewMaster::YTranslate( Event *ev )
{
	if (!current_viewthing) return ;
	
	Viewthing *viewthing = ( Viewthing * )( ( Entity * )current_viewthing );
	Event *originEvent   = new Event(EV_ViewThing_SetOrigin);
	
	originEvent->AddFloat(viewthing->origin.x);
	originEvent->AddFloat(viewthing->origin.y + ev->GetFloat(1));
	originEvent->AddFloat(viewthing->origin.z);
	
	viewthing->ChangeOrigin(originEvent);
	delete originEvent ;
	originEvent = 0 ;
	
	_resetOrigin();
}

void ViewMaster::SetZTranslation( Event *ev )
{
	if (!current_viewthing) return ;
	
	Viewthing *viewthing = ( Viewthing * )( ( Entity * )current_viewthing );
	Event *originEvent = new Event(EV_ViewThing_SetOrigin);
	
	float x = viewthing->origin.x ;
	float y = viewthing->origin.y ;
	originEvent->AddFloat(x);
	originEvent->AddFloat(y);
	originEvent->AddFloat(_lastBaseOrigin.z + ev->GetFloat(1));
	viewthing->ChangeOrigin(originEvent);
	delete originEvent ;
	originEvent = 0 ;
	
	gi.cvar_set( "g_vt_ztrans", va("%0.2f", ev->GetFloat(1)) );
}

void ViewMaster::ZTranslate( Event *ev )
{
	if (!current_viewthing) return ;
	
	Viewthing *viewthing = ( Viewthing * )( ( Entity * )current_viewthing );
	Event *originEvent   = new Event(EV_ViewThing_SetOrigin);
	
	originEvent->AddFloat(viewthing->origin.x);
	originEvent->AddFloat(viewthing->origin.y);
	originEvent->AddFloat(viewthing->origin.z + ev->GetFloat(1));
	
	viewthing->ChangeOrigin(originEvent);
	delete originEvent ;
	originEvent = 0 ;
	
	_resetOrigin();
}

void ViewMaster::Offset( Event *ev )
{
	Viewthing *viewthing;
	Event *new_event;
	
	if ( !current_viewthing ) 
		return;
	
	viewthing = ( Viewthing * )( Entity * )current_viewthing;
	
	new_event = new Event( EV_ViewThing_SetOrigin );
	
	new_event->AddFloat( viewthing->origin.x + ev->GetFloat( 1 ) );
	new_event->AddFloat( viewthing->origin.y + ev->GetFloat( 2 ) );
	new_event->AddFloat( viewthing->origin.z + ev->GetFloat( 3 ) );
	
	viewthing->ProcessEvent( new_event );
	
	_resetOrigin();
}

//================================================================
// Name:        ResetOrigin
// Class:       ViewMaster
//              
// Description: Forces the _lastBaseOrigin to be the origin of the
//              current_viewthing (if any).  Calls _resetOrigin to
//              do the work.
//
//              This is useful because models can be translated off
//              of _lastBaseOrigin and reset to it.  This enables
//              the _lastBaseOrigin to move to where ever the viewthing
//              has been translated too so further relative movements are 
//              relative to this new position.
//              
// Parameters:  Event* -- Not used
//              
// Returns:     None
//              
//================================================================
void ViewMaster::ResetOrigin( Event* )
{
	_resetOrigin();
}

//================================================================
// Name:        PullToCamera
// Class:       ViewMaster
//              
// Description: Moves the current_viewthing (if any) to the current
//              player's (client[0]) position.  Does not drop the
//              object but instead explicitly calls _setToPosition
//              to force the origin of the current_viewthing to be
//              at player's positionn.
//              
// Parameters:  Event* -- Not used
//              
// Returns:     None
//              
//================================================================
void ViewMaster::PullToCamera( Event* )
{
	if (!current_viewthing) return ;
	
	// Check if we have a client
	Entity* clientEntity = g_entities[ 0 ].entity;
	if (!clientEntity) return ;
	
	// Store this new camera position as being where items should spawn here on out
	_lastBaseOrigin = clientEntity->origin ;
	_setToPosition(clientEntity->origin);
}

//================================================================
// Name:        ToggleDrop
// Class:       ViewMaster
//              
// Description: Toggles the g_vt_droptoggle cvar. When the
//              cvar is true, objects are dropped upon spawning.
//              
// Parameters:  Event* -- Not used
//              
// Returns:     None
//              
//================================================================
void ViewMaster::ToggleDrop( Event * )
{
	if (g_vt_droptoggle->integer)
	{
		gi.cvar_set("g_vt_droptoggle", "0");
	}
	else
	{
		gi.cvar_set("g_vt_droptoggle", "1");
	}
}

//================================================================
// Name:        Next
// Class:       ViewMaster
//              
// Description: Takes an event and moves to next spawned viewthing
//              if there is one.  Does not change current_viewthing
//              if there isn't a next viewthing.
//              
// Parameters:  Event* -- Not used
//              
// Returns:     None
//              
//================================================================
void ViewMaster::Next( Event * )
{
	_selectNext();
}

//================================================================
// Name:        Prev
// Class:       ViewMaster
//              
// Description: Responds to an event and moves to previous spawned
//              viewthing if there is one.  Does not change current_viewthing
//              if there isn't a previous viewthing.
//              
// Parameters:  Event* -- Not used
//              
// Returns:     None
//              
//================================================================
void ViewMaster::Prev( Event * )
{
	_selectPrevious();
}

//================================================================
// Name:        DeleteCurrentViewthing
// Class:       ViewMaster
//              
// Description: Support function that simply sends a EV_Remove event
//              to the current viewthing if there is one.  This will
//              delete the current viewthing.
//              
//              Does not modify current_viewthing nor the count of
//              viewthings because it is expected that the caller 
//              handles this.
//
// Parameters:  None
//              
// Returns:     None
//              
//================================================================
void ViewMaster::DeleteCurrentViewthing()
{
	if (current_viewthing)
	{
		// delete all existing models
		Viewthing *viewthing = (Viewthing*)( (Entity*) current_viewthing);
		viewthing->PostEvent( EV_Remove, 0.0f );		
	}
}

//================================================================
// Name:        Delete
// Class:       ViewMaster
//              
// Description: Deletes the current_viewthing if there is one.  Attempts
//              to set the current_viewthing to the previous one in the
//              list of viewthings.  If it cannot do that, it sets it
//              to the next one.  If it cannot do that, it sets current_viewthing
//              to 0 (NULL).
//              
// Parameters:  Event* -- Not used
//              
// Returns:     None
//              
//================================================================
void ViewMaster::Delete( Event * )
{
	if (!current_viewthing) return ;
	
	// Save off the one to delete
	Viewthing *viewthing = (Viewthing*)( (Entity*) current_viewthing);
	
	_selectPrevious();                  // Try and select previous
	
	if (current_viewthing == viewthing) // If same as one we are deleting, there wasn't a previous
	{
		_selectNext();                  // Try and select next
	}
	
	if (current_viewthing == viewthing) // If same as one we are deleting, there wasn't a next
	{
		current_viewthing = 0 ;         // set it to 0
	}
	
	// Delete the one we saved off
	viewthing->PostEvent( EV_Remove, 0.0f );
	_updateViewthingCounts(-1);         // we pass -1 here because there will be one less viewthing but it hasn't actually been deleted yet
}

//================================================================
// Name:        DeleteAll
// Class:       ViewMaster
//              
// Description: Deletes all viewspawn things currently in memory.
//              
// Parameters:  None
//              
// Returns:     None
//              
//================================================================
void ViewMaster::DeleteAll( Event * )
{
	Entity *next;
	
	for( next = G_FindClass( NULL, "viewthing" ); next != NULL; next = G_FindClass( next, "viewthing" ) )
	{
		next->PostEvent( EV_Remove, 0.0f );
	}
	
	current_viewthing = NULL;
}

//================================================================
// Name:        spawnAtPosition
// Class:       ViewMaster
//              
// Description: Spawns the specified tiki model at the specified
//              position.  The current_viewthing will point to this
//              new viewthing if the spawn succeeds, 0 if it doesn't.
//              
// Parameters:  const str&    -- name of tiki model ("models/eliza.tik")
//              const Vector& -- Position to spawn model at
//
// Returns:     None
//              
//================================================================
void ViewMaster::spawnAtPosition( const str& modelName, const Vector& pos )
{
	// create a new viewthing
	Viewthing *viewthing  = new Viewthing;
	current_viewthing     = viewthing;
	viewthing->baseorigin = pos ;
	viewthing->setOrigin( viewthing->baseorigin );
	_lastBaseOrigin = viewthing->baseorigin ;
	
	if (g_vt_droptoggle->integer)
	{
		viewthing->droptofloor( 10000.0f );
	}
	
	viewthing->setAngles( vec_zero );
	
	Event* event = new Event( EV_ViewThing_SetModel );
	event->AddString( modelName );
	viewthing->ProcessEvent( event );
	
	if ( !gi.IsModel( viewthing->edict->s.modelindex ) )
	{
		delete viewthing;
		current_viewthing = 0 ;
		return;
	}
	_updateViewthingCounts();
}

//================================================================
// Name:			SpawnFromTS
// Class:			ViewMaster
//
// Description:		Called by the ToolServer to viewspawn a model
//					the ToolServer sends over dynamically.
//
// Parameters:		Event *ev -- Point to event whose first string argument
//                        is name of tiki model to reload, the second string
//						  is the tiki to respawn.  If the second string isn't
//						  specified, it respawns the first string
//
// Returns:			None
//
//================================================================
void ViewMaster::SpawnFromTS( Event *ev )
{
	char *data = (char*)gi.ToolServerGetData();
	
	if (!data) // No data? Just abort.
		return;
	
	DeleteAll(NULL); // Overkill?
	
	char spawnmodel[256], reloadmodel[256];
	
	strcpy(reloadmodel, "models/");
	strcat(reloadmodel, ev->GetString(1));
	
	if ( ev->NumArgs() > 1 )
	{
		// Already has models/ on it
		strcpy(spawnmodel, ev->GetString(2));
	}
	else
	{
		strcpy(spawnmodel,"models/");
		strcat(spawnmodel,  ev->GetString(1));
	}
	
	gi.DPrintf("Reloading Model: %s...\n",reloadmodel);
	gi.TikiLoadFromTS(reloadmodel, data);
	// Setting this config string causes the client to reload the surfaces for this tiki
	gi.setConfigstring( CS_DEBUGINFO, va( "reload %s %d", spawnmodel, gi.modelindex(reloadmodel) ) );
	
	// Create a new spawn event with item name to send to Spawn function
	Event* spawnEvent = new Event( EV_ViewThing_Spawn);
	spawnEvent->AddString(spawnmodel);
	Spawn(spawnEvent);
	delete spawnEvent;
	spawnEvent = 0 ;
	
	gi.setConfigstring( CS_DEBUGINFO, "" );
}

//================================================================
// Name:        Spawn
// Class:       ViewMaster
//              
// Description: Spawns the tiki model specified in the Event's
//              first string argument.  The current_viewthing points
//              to the spawned object if the spawn succeeds, 0
//              if it doesn't.
// 
//              The spawned object is dropped straight down if
//              the cvar g_vt_droptoggle is set to true (default).
//              This will cause objects to spawn on the ground.
//              
// Parameters:  Event* -- Point to event whose first string argument
//                        is name of tiki model to spawn ("model/eliza.tik")
//              
// Returns:     None
//              
//================================================================
void ViewMaster::Spawn( Event *ev )
{
	const char	*mdl;
	Viewthing	*viewthing;
	Entity		*ent;
	Event		*event;
	Vector		 forward;
	Vector		 up;
	Vector		 delta;
	
	mdl = ev->GetString( 1 );
	if ( !mdl || !mdl[ 0 ] )
	{
		ev->Error( "Must specify a model name" );
		return;
	}
	
	// Check if we have a client
	ent = g_entities[ 0 ].entity;
	assert( ent );
	
	//-----------------------------------------
	// This if block attempts to keep the current
	// model index of a set up to date when an explicit
	// viewspawn command is issued.  If the model that's
	// being spawn is within the set, the index in the
	// set will be updated accordingly.
	// 
	// If it isn't found in the set, we simply continue.
	//-----------------------------------------
	if (_numberOfModelsInSet)
	{
		str modelName(mdl);
		int pos = g_vt_modelIndex->integer ;
		if ((pos > 1) && (pos < _modelNamesArray.NumObjects()) && (_modelNamesArray.ObjectAt(pos) != modelName) )
		{
			int pos = _modelNamesArray.IndexOfObject(modelName);
			if (pos)
			{
				gi.cvar_set( "g_vt_modelIndex", va("%d", pos) );
				DeleteCurrentViewthing();
				DisplayCurrentModelInSet();
				return ;
			}
		}
	}
	
	// create a new viewthin and set current_viewthing to it.
	viewthing         = new Viewthing;
	current_viewthing = viewthing;
	
	//FIXME FIXME
	ent->angles.AngleVectors( &forward, NULL, &up );
	
	// If _spawnAtLastItemsOrigin is set, we will spawn on
	if (_spawnAtLastItemsOrigin)
	{
		float upOffset = (g_vt_droptoggle->integer) ? 48 : 0 ;
		viewthing->baseorigin = _lastBaseOrigin ;
		viewthing->baseorigin += forward ;
		viewthing->baseorigin += (up * upOffset);
	}
	else {
		viewthing->baseorigin = ent->origin;
		viewthing->baseorigin += ( forward * 48.0f );
		viewthing->baseorigin += ( up * 48.0f );
	}
	
	viewthing->setOrigin( viewthing->baseorigin );
	
	// Added this so that guns and such can be prevented from falling into the floor.
	// Controlled by viewmodeltoggledrop command.  Default is true, so same as old functionality
	if (g_vt_droptoggle->integer)
	{
		viewthing->droptofloor( 10000.0f );
	}

	viewthing->baseorigin = viewthing->origin;
	_lastBaseOrigin = viewthing->baseorigin ;

	delta = ent->origin - viewthing->origin;
	viewthing->setAngles( vec_zero );

	event = new Event( EV_ViewThing_SetModel );
	event->AddString( mdl );
	viewthing->ProcessEvent( event );

	if ( !gi.IsModel( viewthing->edict->s.modelindex ) )
	{
		ev->Error( "model %s not found, viewmodel not spawned.", mdl );
		delete viewthing;
		current_viewthing = NULL;
		return;
	}

	_updateViewthingCounts();
}

//================================================================
// Name:        SetModelEvent
// Class:       ViewMaster
//              
// Description: Sets the model for the current viewthing.  Passes
//              a SetModelEvent down to the current viewthing. The
//              Event must contain the model name as its first string
//              argument.
//              
// Parameters:  Event* -- First string argument contains model name
//              
// Returns:     None
//              
//================================================================
void ViewMaster::SetModelEvent( Event *ev )
{
	const char	*mdl;
	char			str[ 128 ];
	Event			*event;
	Viewthing	*viewthing;
	
	mdl = ev->GetString( 1 );
	if ( !mdl || !mdl[ 0 ] )
	{
		ev->Error( "Must specify a model name" );
		return;
	}
	
	if ( !current_viewthing )
	{
		// try to find one on the map
		current_viewthing = G_FindClass( NULL, "viewthing" );
		if ( !current_viewthing )
		{
			ev->Error( "No viewmodel" );
			return;
		}
	}
	
	viewthing = ( Viewthing * )( ( Entity * )current_viewthing );
	
	// Prepend 'models/' to make things easier
	str[ 0 ] = 0;
	if ( ( mdl[ 1 ] != ':' ) && strnicmp( mdl, "models", 6 ) )
	{
		strcpy( str, "models/" );
	}
	strcat( str, mdl );
	
	event = new Event( EV_ViewThing_SetModel );
	event->AddString( str );
	viewthing->ProcessEvent( event );
	viewthing->UpdateCvars();
}

//================================================================
// Name:        PassEvent
// Class:       ViewMaster
//              
// Description: Passes an event down to the current_viewthing if
//              it is set.
//              
// Parameters:  Event* -- Event to pass
//              
// Returns:     None
//              
//================================================================
void ViewMaster::PassEvent( Event *ev )
{
	Viewthing *viewthing;
	Event *event;
	
	if ( !current_viewthing )
	{
		ev->Error( "No viewmodel" );
		return;
	}
	
	viewthing = ( Viewthing * )( ( Entity * )current_viewthing );
	if ( viewthing )
	{
		event = new Event( ev );
		viewthing->ProcessEvent( event );
	}
}


//---------------------------------------------------------------------------
//              P R O T E C T E D   M E T H O D S 
//---------------------------------------------------------------------------


//================================================================
// Name:        _resetOrigin
// Class:       ViewMaster
//              
// Description: Sets the origin for the current viewthing to be wherever
//              it is at.  This also sets the _lastBaseOrigin, which
//              means further relative translations are based from here.
//              
// Parameters:  None
//              
// Returns:     None
//              
//================================================================
void ViewMaster::_resetOrigin()
{
	if (!current_viewthing) return ;
	
	Viewthing *viewthing = ( Viewthing*) ( (Entity*) current_viewthing);
	_lastBaseOrigin = viewthing->origin ;
	_setToPosition(_lastBaseOrigin); // just ensure we update cvars
}

//================================================================
// Name:        _setToPosition
// Class:       ViewMaster
//              
// Description: Sets the Position of the current viewthing.
//              
// Parameters:  Vector& pos -- vector of position to set origin to
//              
// Returns:     None
//              
//================================================================
void ViewMaster::_setToPosition( const Vector& pos )
{
	// Create a new event to pass to viewthing with new origin
	Event* originEvent = new Event(EV_ViewThing_SetOrigin);
	
	originEvent->AddFloat(pos.x);
	originEvent->AddFloat(pos.y);
	originEvent->AddFloat(pos.z);
	
	// Pass to the viewthing and clean up
	Viewthing *viewthing = (Viewthing*)( (Entity*)current_viewthing );
	viewthing->ChangeOrigin(originEvent);
	delete originEvent ;
	originEvent = 0 ;
	
	gi.cvar_set( "g_vt_xtrans", "0" );
	gi.cvar_set( "g_vt_ytrans", "0" );
	gi.cvar_set( "g_vt_ztrans", "0" );
}

//================================================================
// Name:        _selectNext
// Class:       ViewMaster
//              
// Description: Selects the next viewthing in the set of viewthings
//              Selects none if there are no more viewthings.
//
// Parameters:  None
//              
// Returns:     None
//              
//================================================================
void ViewMaster::_selectNext()
{
	Viewthing *viewthing;
	Entity * ent;
	
	ent = G_FindClass( current_viewthing, "viewthing" );
	if ( ent )
	{
		if (current_viewthing) 
		{
			viewthing = ( Viewthing * )( ( Entity * )current_viewthing );				
			viewthing->SetSelected( false );
		}
		
		current_viewthing = ent;
		viewthing = ( Viewthing * )( ( Entity * )current_viewthing );
		_lastBaseOrigin = viewthing->origin ;
		viewthing->UpdateCvars();
		viewthing->SetSelected( true );
		gi.Printf( "current viewthing model %s.\n", viewthing->model.c_str() );
		_updateViewthingCounts();
	}
}

//================================================================
// Name:        _selectPrev
// Class:       ViewMaster
//              
// Description: Selects the previous viewthing in the set of viewthings.
//              Selects none if there are no previous viewthings.
//              
// Parameters:  None
//              
// Returns:     None
//              
//================================================================
void ViewMaster::_selectPrevious()
{
	Viewthing   *viewthing;
	Entity      *prev;
	Entity      *next;
	
	next = NULL;
	do
	{
		prev = next;
		next = G_FindClass( prev, "viewthing" );
	}
	while( next != current_viewthing );
	
	if ( prev )
	{
		if (current_viewthing) 
		{
			viewthing = ( Viewthing * )( ( Entity * )current_viewthing );				
			viewthing->SetSelected( false );
		}
		
		current_viewthing = prev;
		viewthing = ( Viewthing * )( ( Entity * )current_viewthing );
		_lastBaseOrigin = viewthing->origin ;
		viewthing->UpdateCvars();
		viewthing->SetSelected(true);
		gi.Printf( "current viewthing model %s.\n", viewthing->model.c_str() );
		_updateViewthingCounts();
	}
}		

//================================================================
// Name:        _updateViewthingCounts
// Class:       ViewMaster
//              
// Description: Sets the cvars g_vt_viewnumber and g_vt_viewcount
//              appropriately after any change to the number of 
//              spawned viewthings in the world.  When the loop
//              finishes, g_vt_viewcount is the total number of 
//              spawned viewthings and g_vt_viewnumber is position
//              in this list of the current_viewthing.
//              
//              The passed in parameter can be used for manipulating
//              the final count.  Because deletion of viewthings is
//              event driven, when we know there is going to be a
//              deletion the deletion hasn't actually occured yet.
//              So it would show up in the count.  Hence, we can pass
//              in a -1 to offset the final count appropriately.
//              Hack.
//
// Parameters:  int countAdjustment -- offset final count (default is 0).
//              
// Returns:     None
//              
//================================================================
void ViewMaster::_updateViewthingCounts(int countAdjustment)
{
	Entity* next = G_FindClass( NULL, "viewthing");
	int     numberOfViewthings    = 0 ;
	int     currentViewthingIndex = 0 ;
	
	while (next)
	{
		numberOfViewthings++ ;
		if (next == current_viewthing)
		{
			currentViewthingIndex = numberOfViewthings ;
		}
		next = G_FindClass( next, "viewthing");
	}
	
	gi.cvar_set( "g_vt_viewnumber", va("%d", currentViewthingIndex) );
	gi.cvar_set( "g_vt_viewcount",  va("%d", numberOfViewthings + countAdjustment) );
}

//================================================================
// Name:        _randomizeViewthing
// Class:       ViewMaster
//              
// Description: Randomizes the scaling and rotation of the specified
//              viewthing, based on various cvar settings.  These
//              randomizations can be turned on and off individually,
//              but they are only applied if g_vt_viewrandomize is true.
//              
//              The bell curve randomization is done in the scaling section.
//              It works like this:
//                o Add two random values between 0 and 100 and divide total by 100.  (Bell curve value--try with two dice)
//                o Get half distance between desired low and high ((high-low) divided by 2)
//                o Scale this half-distance by the bell curve value computed in step 1 (yields # from 0 to 2*half, but tends towards halfway)
//                o Add scaled half to low value, giving bell curve between desired low and high
//
// Parameters:  Viewthing* -- viewthing to randomize
//              
// Returns:     None
//              
//================================================================
void ViewMaster::_randomizeViewthing( Viewthing* viewthingPtr )
{
	if (!viewthingPtr)
	{
		gi.Printf( "No viewthing to randomize\n");
		return ;
	}
	
	if (g_vt_viewsrandomize->integer)
	{
		// Randomly scale
		float die1        = G_Random( 100.0f );
		float die2        = G_Random( 100.0f );
		float scaleValue  = (die1 + die2) / 100.0f ;
		
		float midpoint    = ( (float)g_vt_viewmaxrandomize->integer - (float)g_vt_viewminrandomize->integer) / 2.0f ;
		float adjustment  = (float)g_vt_viewminrandomize->integer + (midpoint * scaleValue);
		float scaleFactor = (adjustment / 100.0f);
		
		Event* scaleEvent = new Event(EV_ViewThing_SetScale);
		scaleEvent->AddFloat(scaleFactor);
		viewthingPtr->SetScaleEvent(scaleEvent);
		delete scaleEvent ;
		scaleEvent = 0 ;
	}
	
	if (g_vt_viewzrandomize->integer)
	{
		// Randomly rotate around z-axis
		Event* rotateEvent = new Event(EV_ViewThing_SetYaw);
		rotateEvent->AddFloat(G_Random(360.0f));
		viewthingPtr->SetYawEvent(rotateEvent);
		delete rotateEvent ;
		rotateEvent = 0 ;
	}
	
	if (g_vt_viewyrandomize->integer)
	{
		// Randomly rotate around z-axis
		Event* rotateEvent = new Event(EV_ViewThing_SetPitch);
		rotateEvent->AddFloat(G_Random(360.0f));
		viewthingPtr->SetPitchEvent(rotateEvent);
		delete rotateEvent ;
		rotateEvent = 0 ;
	}
	
	if (g_vt_viewxrandomize->integer)
	{
		// Randomly rotate around z-axis
		Event* rotateEvent = new Event(EV_ViewThing_SetRoll);
		rotateEvent->AddFloat(G_Random(360.0f));
		viewthingPtr->SetRollEvent(rotateEvent);
		delete rotateEvent ;
		rotateEvent = 0 ;
	}
}

//------------------------------------------------------------------------
//
//                          VIEWTHING
//
//------------------------------------------------------------------------

CLASS_DECLARATION( Entity, Viewthing, "viewthing" )
{
	{ &EV_ViewThing_Think,				&Viewthing::ThinkEvent },
	{ &EV_ViewThing_LastFrame,     		&Viewthing::LastFrameEvent },
	{ &EV_ViewThing_ToggleAnimate,		&Viewthing::ToggleAnimateEvent },
	{ &EV_ViewThing_SetModel,			&Viewthing::SetModelEvent },
	{ &EV_ViewThing_NextFrame,			&Viewthing::NextFrameEvent },
	{ &EV_ViewThing_PrevFrame,			&Viewthing::PrevFrameEvent },
	{ &EV_ViewThing_NextAnim,			&Viewthing::NextAnimEvent },
	{ &EV_ViewThing_PrevAnim,			&Viewthing::PrevAnimEvent },
	{ &EV_ViewThing_ScaleUp,			&Viewthing::ScaleUpEvent },
	{ &EV_ViewThing_ScaleDown,			&Viewthing::ScaleDownEvent },
	{ &EV_ViewThing_SetScale,			&Viewthing::SetScaleEvent },
	{ &EV_ViewThing_SetYaw,				&Viewthing::SetYawEvent },
	{ &EV_ViewThing_SetPitch,			&Viewthing::SetPitchEvent },
	{ &EV_ViewThing_SetRoll,			&Viewthing::SetRollEvent },
	{ &EV_ViewThing_SetAngles,			&Viewthing::SetAnglesEvent },
	{ &EV_ViewThing_SetStatic,          &Viewthing::SetStatic },
	{ &EV_ViewThing_Attach,				&Viewthing::AttachModel },
	{ &EV_ViewThing_Detach,				&Viewthing::Delete },
	{ &EV_ViewThing_DetachAll, 			&Viewthing::DetachAll },
	{ &EV_ViewThing_Delete,				&Viewthing::Delete },
	{ &EV_ViewThing_SetOrigin,			&Viewthing::ChangeOrigin },
	{ &EV_ViewThing_SaveOffSurfaces,	&Viewthing::SaveSurfaces },
	{ &EV_ViewThing_PrintTime,			&Viewthing::PrintTime },
	{ &EV_ViewThing_SetAnim,			&Viewthing::SetAnim },
	{ &EV_ViewThing_NextMorph,			&Viewthing::NextMorph },
	{ &EV_ViewThing_PrevMorph,			&Viewthing::PrevMorph },
	{ &EV_ViewThing_Morph,				&Viewthing::Morph },
	{ &EV_ViewThing_Unmorph,			&Viewthing::Unmorph },
	{ &EV_ViewThing_ChangeRoll,         &Viewthing::ChangeRollEvent },
	{ &EV_ViewThing_ChangePitch,        &Viewthing::ChangePitchEvent },
	{ &EV_ViewThing_ChangeYaw,          &Viewthing::ChangeYawEvent },
	{ &EV_ViewThing_Flash,              &Viewthing::Flash },
	{ &EV_ViewThing_SetFrame,           &Viewthing::SetFrame },

	{ NULL, NULL }
};

Viewthing::Viewthing( void )
{
	animate    = new Animate( this );
	frame       = 0;
	animstate   = 0;
	_static     = (g_vt_makestatic->integer != 0) ? true : false ;
	_selected   = false ;
	_pulseCount = 0 ;
	
	setSolidType( SOLID_NOT );
	baseorigin = origin;
	Viewmodel.current_viewthing = this;
	edict->s.eType = ET_MODELANIM;
	edict->s.renderfx |= RF_SHADOW;
	edict->s.renderfx |= RF_SHADOW_PRECISE;
	edict->s.renderfx |= RF_EXTRALIGHT;
	
	current_morph = 0;
	
	gi.cvar_set( "g_vt_scale",  "1.0" );
	gi.cvar_set( "g_vt_roll",   "0.0" );
	gi.cvar_set( "g_vt_pitch",  "0.0" );
	gi.cvar_set( "g_vt_yaw",    "0.0" );
	gi.cvar_set( "g_vt_xtrans", "0.0" );
	gi.cvar_set( "g_vt_ytrans", "0.0" );
	gi.cvar_set( "g_vt_ztrans", "0.0" );
	
	
	// save off surfaces once the model is spawned
	PostEvent( EV_ViewThing_SaveOffSurfaces, FRAMETIME );
	
	PostEvent( EV_ViewThing_Think, FRAMETIME );
}


void Viewthing::SetSelected( qboolean state )
{
	_selected   = state ;
	_pulseCount = 0 ;
	
	if (this->hidden())
	{
		this->showModel();
	}
}

void Viewthing::Flash( Event *ev )
{
	SetSelected(true);
}

void Viewthing::PrintTime( Event *ev )
{
	gi.Printf( "ev current frame %d leveltime %.2f\n", ev->GetInteger( 1 ), level.time );
}

void Viewthing::UpdateCvars( qboolean quiet )
{
	
	gi.cvar_set( "viewmodelanim",      animate->AnimName() );
	gi.cvar_set( "viewmodeltotaltime", va( "%.3f", animate->AnimTime()) );
	gi.cvar_set( "viewmodelanimframes",va( "%d", animate->NumFrames()) );
	gi.cvar_set( "viewmodelframe",     va( "%d", CurrentFrame() ) );
	gi.cvar_set( "viewmodelname",      model.c_str() );
	gi.cvar_set( "viewmodelscale",     va( "%0.2f", edict->s.scale ) );
	gi.cvar_set( "currentviewthing",   model.c_str() );
	gi.cvar_set( "viewthingorigin",    va( "%0.2f,%0.2f,%0.2f", edict->s.origin[0],edict->s.origin[1],edict->s.origin[2] ) );
	gi.cvar_set( "viewmodelanimnum",   va( "%.3f", ( float )CurrentAnim() / ( float )animate->NumAnims() ) );
	gi.cvar_set( "g_vt_scale",         va( "%.2f", edict->s.scale) );
	gi.cvar_set( "g_vt_roll",          va( "%.2f", (angles.z <= 180.0f) ? angles.z : (angles.z - 360.0f)) );
	gi.cvar_set( "g_vt_pitch",         va( "%.2f", (angles.x <= 180.0f) ? angles.x : (angles.x - 360.0f)) );
	gi.cvar_set( "g_vt_yaw",           va( "%.2f", (angles.y <= 180.0f) ? angles.y : (angles.y - 360.0f)) );
	gi.cvar_set( "g_vt_xtrans",        va( "%.2f", (Viewmodel._lastBaseOrigin.x - baseorigin.x) ) );
	gi.cvar_set( "g_vt_ytrans",        va( "%.2f", (Viewmodel._lastBaseOrigin.y - baseorigin.y) ) );
	gi.cvar_set( "g_vt_ztrans",        va( "%.2f", (Viewmodel._lastBaseOrigin.z - baseorigin.z) ) );
	
	if ( gi.Morph_NameForNum( edict->s.modelindex, current_morph ) )
		gi.cvar_set( "viewmorphname", gi.Morph_NameForNum( edict->s.modelindex, current_morph ) );
	else
		gi.cvar_set( "viewmorphname", "" );
	
	if ( !quiet )
	{
		gi.Printf( "%s, frame %3d, scale %4.2f\n", animate->AnimName(), CurrentFrame(), edict->s.scale );
	}
}


void Viewthing::ThinkEvent( Event *ev )
{
	static float startTime = 0 ;
	
	if (_selected && (_pulseCount < 20)) 
	{
		if (startTime == 0.0f)
		{
			startTime = level.time ;
		}
		
		if ( (level.time - startTime) > 0.15f)
		{
			if (this->hidden())
			{
				this->showModel();
			}
			else 
			{
				this->hideModel();
			}
			startTime = 0.0f ;
			_pulseCount++ ;
		}
	}
	
	
	int f;
	if (animstate >= 2)
	{
		Vector   forward;
		Vector	left;
		Vector	up;
		Vector   realmove;
		
		angles.AngleVectors( &forward, &left, &up );
		realmove = ( left * total_delta[1] ) + ( up * total_delta[2] ) + ( forward * total_delta[0] );
		setOrigin( baseorigin + realmove );
		gi.cvar_set( "viewthingorigin", va( "%0.2f,%0.2f,%0.2f", edict->s.origin[0],edict->s.origin[1],edict->s.origin[2] ) );
	}
	PostEvent( EV_ViewThing_Think, FRAMETIME );
	if ( ( animstate > 0 ) && ( Viewmodel.current_viewthing == this ) )
	{
		f = CurrentFrame();
		if ( f != lastframe )
		{
			float time;
			lastframe = f;
			time = f * animate->AnimTime() / (float)animate->NumFrames();
			gi.Printf( "current frame %d time %.2f\n", f, time );
			gi.cvar_set( "viewmodeltime", va( "%.2f", time ) );
			gi.cvar_set( "viewmodelframe", va( "%d", f ) );
			gi.cvar_set( "viewmodelanim", animate->AnimName() );
		}
	}
}

void Viewthing::LastFrameEvent( Event *ev )
{
	if ( animstate != 3 )
	{
		total_delta = Vector(0, 0, 0);
	}
}

void Viewthing::ToggleAnimateEvent( Event *ev )
{
	animstate = ( animstate + 1 ) % 4;
	total_delta = Vector(0, 0, 0);
	setOrigin( baseorigin );
	
	// reset to a known state
	switch( animstate )
	{
	case 0:
		animate->SetFrame( frame );
		gi.Printf( "Animation stopped.\n" );
		gi.cvar_set( "viewmodelanimmode", "Stopped" );
		break;
	case 1:
		animate->NewAnim( CurrentAnim() );
		gi.Printf( "Animation no motion.\n" );
		gi.cvar_set( "viewmodelanimmode", "No Motion" );
		break;
	case 2:
		animate->NewAnim( CurrentAnim(), EV_ViewThing_LastFrame );
		gi.Printf( "Animation with motion and looping.\n" );
		gi.cvar_set( "viewmodelanimmode", "Motion and Looping" );
		break;
	case 3:
		animate->NewAnim( CurrentAnim(), EV_ViewThing_LastFrame );
		gi.Printf( "Animation with motion no looping.\n" );
		gi.cvar_set( "viewmodelanimmode", "Motion and No Looping" );
		break;
	}
	UpdateCvars( true );
}

void Viewthing::SetStatic( Event *ev )
{
	if (ev->NumArgs() == 1) 
	{
		_static = (ev->GetInteger(1)) ? true : false ;
	}
	else
	{
		_static = (g_vt_makestatic->integer != 0) ? true : false ;
	}
}

void Viewthing::SetModelEvent( Event *ev )
{
	str modelname;
	
	modelname = ev->GetString( 1 );
	
	setModel( modelname );
	
	if ( gi.IsModel( edict->s.modelindex ) )
	{
		// Default the viewthing to an idle animation if the model has one
		
		if ( animate->HasAnim( "idle" ) )
			animate->RandomAnimate( "idle" );
		else
			animate->NewAnim( 0 );
		
		frame = 0;
		animate->SetFrame( 0 );
	}
	
	UpdateCvars();
}

void Viewthing::NextFrameEvent( Event *ev )
{
	int numframes;
	
	numframes = animate->NumFrames();
	if ( numframes )
	{
		frame = (frame+1)%numframes;
		animate->SetFrame( frame );
		animstate = 0;
		UpdateCvars();
	}
}

void Viewthing::PrevFrameEvent( Event *ev )
{
	int numframes;
	
	numframes = animate->NumFrames();
	if ( numframes )
	{
		frame = (frame-1)%numframes;
		animate->SetFrame( frame );
		animstate = 0;
		UpdateCvars();
	}
}

void Viewthing::SetAnim( Event *ev )
{
	int numanims, anim;
	
	numanims = animate->NumAnims();
	if ( numanims )
	{
		// restore original surfaces
		memcpy( edict->s.surfaces, origSurfaces, sizeof( origSurfaces ) );
		
		anim = ev->GetFloat( 1 ) * numanims;
		if ( anim >= numanims )
			anim = numanims - 1;
		animate->NewAnim( anim % numanims );
		frame = 0;
		animate->SetFrame( frame );
		animstate = 0;
		UpdateCvars();
	}
}

void Viewthing::SetAnim( int num )
{
	int numanims;
	
	numanims = animate->NumAnims();
	if ( numanims )
	{
		// restore original surfaces
		memcpy( edict->s.surfaces, origSurfaces, sizeof( origSurfaces ) );
		
		// if ( num >= numanims )
		//   num = numanims - 1;
		animate->NewAnim( num );
		animate->SetFrame( frame );
		//animstate = 0;
		UpdateCvars();
	}
}

void Viewthing::NextAnimEvent( Event *ev )
{
	int numanims;
	
	numanims = animate->NumAnims();
	if ( numanims )
	{
		// restore original surfaces
		memcpy( edict->s.surfaces, origSurfaces, sizeof( origSurfaces ) );
		
		animate->NewAnim( ( CurrentAnim() + 1 ) % numanims );
		frame = 0;
		animate->SetFrame( frame );
		animstate = 0;
		gi.Printf( "Animation stopped.\n" );
		gi.cvar_set( "viewmodelanimmode", "Stopped" );
		UpdateCvars();
	}
}

void Viewthing::PrevAnimEvent( Event *ev )
{
	int anim;
	int numanims;
	
	numanims = animate->NumAnims();
	if ( numanims )
	{
		// restore original surfaces
		memcpy( edict->s.surfaces, origSurfaces, sizeof( origSurfaces ) );
		
		anim = CurrentAnim() - 1;
		while( anim < 0 )
		{
			anim += numanims;
		}
		animate->NewAnim( anim );
		frame = 0;
		animate->SetFrame( frame );
		animstate = 0;
		gi.Printf( "Animation stopped.\n" );
		gi.cvar_set( "viewmodelanimmode", "Stopped" );
		UpdateCvars();
	}
}

void Viewthing::ScaleUpEvent( Event *ev )
{
	edict->s.scale += 0.05f;
	UpdateCvars();
}

void Viewthing::ScaleDownEvent( Event *ev )
{
	edict->s.scale -= 0.05f;
	UpdateCvars();
}

void Viewthing::SetScaleEvent( Event *ev )
{
	if ( ev->NumArgs() )
	{
		float s = ev->GetFloat( 1 );
		edict->s.scale = s;
		UpdateCvars();
	}
}

void Viewthing::SetYawEvent( Event *ev )
{
	if ( ev->NumArgs() > 0 )
	{
		angles.setYaw( ev->GetFloat( 1 ) );
		setAngles( angles );
		UpdateCvars();
	}
}

void Viewthing::SetPitchEvent( Event *ev )
{
	if ( ev->NumArgs() > 0 )
	{
		angles.setPitch( ev->GetFloat( 1 ) );
		setAngles( angles );
		UpdateCvars();
	}
}

void Viewthing::SetRollEvent( Event *ev )
{
	if ( ev->NumArgs() > 0 )
	{
		angles.setRoll( ev->GetFloat( 1 ) );
		setAngles( angles );
		UpdateCvars();
	}
}

void Viewthing::ChangeRollEvent( Event *ev )
{
	if ( ev->NumArgs() > 0)
	{
		angles.setRoll(angles.roll() + ev->GetFloat( 1 ) );
		setAngles(angles);
		UpdateCvars();
	}
}

void Viewthing::ChangePitchEvent( Event *ev )
{
	if ( ev->NumArgs() > 0)
	{
		angles.setPitch(angles.pitch() + ev->GetFloat( 1 ) );
		setAngles(angles);
		UpdateCvars();
	}
}

void Viewthing::ChangeYawEvent( Event *ev )
{
	if ( ev->NumArgs() > 0)
	{
		angles.setYaw(angles.yaw() + ev->GetFloat( 1 ) );
		setAngles(angles);
		UpdateCvars();
	}
}

void Viewthing::SetAnglesEvent( Event *ev )
{
	if ( ev->NumArgs() > 2 )
	{
		angles.x = ev->GetFloat( 1 );
		angles.y = ev->GetFloat( 2 );
		angles.z = ev->GetFloat( 3 );
		setAngles( angles );
	}
	gi.cvar_set( "g_vt_roll",  va ("%0.2f", angles.x) );
	gi.cvar_set( "g_vt_pitch", va ("%0.2f", angles.y) );
	gi.cvar_set( "g_vt_yaw",   va ("%0.2f", angles.z) );
	gi.Printf( "angles = %0.4f, %0.4f, %0.4f\n", angles.x, angles.y, angles.z );
}

//--------------------------------------------------------------
//
// Name:			AttachModel
// Class:			Viewthing
//
// Description:		Attaches a model to a viewspawned model
//
// Parameters:		Event *ev
//						string -- model name
//
// Returns:			None
//
//--------------------------------------------------------------
void Viewthing::AttachModel(Event *ev)
{
	Event *event = 0;
	Viewthing *child = 0;
	str mdl = ev->GetString(2);

	child = new Viewthing;
	child->setModel( mdl.c_str() );

	// Check to make sure it exists.
	if ( !gi.IsModel( child->edict->s.modelindex ) )
	{
		ev->Error( "attachmodel %s not found, attachmodel not spawned.", mdl.c_str() );
		delete child;
		Viewmodel.ProcessEvent(EV_ViewThing_Prev);
		return;
	}

	// Attach the child
	event = new Event( EV_Attach );
	event->AddEntity( this );
	event->AddString( ev->GetString( 1 ) );
	child->ProcessEvent( event );
}

void Viewthing::Delete( Event *ev )
{
	Viewmodel.current_viewthing = NULL;
	PostEvent( EV_Remove, 0.0f );
	Viewmodel.PostEvent( EV_ViewThing_Next, 0.0f );
}

void Viewthing::Delete()
{
	PostEvent( EV_Remove, 0.0f );
}

void Viewthing::DetachAll( Event *ev )
{
	int i;
	int num;
	Entity * ent;
	
	if ( bind_info )
	{
		num = bind_info->numchildren;
		
		for (i=0;i<MAX_MODEL_CHILDREN;i++)
		{
			if (!bind_info->children[i])
				continue;
			
			ent = ( Entity * )G_GetEntity( bind_info->children[i] );
			ent->PostEvent( EV_Remove, 0.0f );
			num--;
			
			if (!num)
				break;
		}
	}
}

void Viewthing::ChangeOrigin( Event *ev )
{
	if ( ev->NumArgs() )
	{
		origin.x = ev->GetFloat( 1 );
		origin.y = ev->GetFloat( 2 );
		origin.z = ev->GetFloat( 3 );
		setOrigin( origin );
		baseorigin = origin;
		gi.cvar_set( "g_vt_height", va ("%f", origin.z) );
		UpdateCvars();
	}
	gi.Printf( "vieworigin = x%0.4f y%0.4f z%0.4f\n", origin.x, origin.y, origin.z );
}

void Viewthing::SaveSurfaces( Event *ev )
{
	memcpy( origSurfaces, edict->s.surfaces, sizeof( origSurfaces ) );
}


void Viewthing::NextMorph( Event *ev )
{
	current_morph++;
	
	if ( current_morph >= gi.NumMorphs( edict->s.modelindex ) )
		current_morph = 0;
	
	if ( gi.Morph_NameForNum( edict->s.modelindex, current_morph ) )
		gi.Printf( "morph %s\n", gi.Morph_NameForNum( edict->s.modelindex, current_morph ) );
	
	UpdateCvars( true );
}

void Viewthing::PrevMorph( Event *ev )
{
	current_morph--;
	
	if ( current_morph < 0 )
		current_morph = gi.NumMorphs( edict->s.modelindex ) - 1;
	
	if ( gi.Morph_NameForNum( edict->s.modelindex, current_morph ) )
		gi.Printf( "morph %s\n", gi.Morph_NameForNum( edict->s.modelindex, current_morph ) );
	
	UpdateCvars( true );
}

void Viewthing::Morph( Event *ev )
{
	const char *morph_name;
	
	morph_name = gi.Morph_NameForNum( edict->s.modelindex, current_morph );
	
	if ( morph_name )
	{
		Event *new_event;
		
		new_event = new Event ( EV_Morph );
		new_event->AddString( morph_name );
		//new_event->AddFloat( 50 );
		ProcessEvent( new_event );
	}
}

void Viewthing::Unmorph( Event *ev )
{
	const char *morph_name;
	
	morph_name = gi.Morph_NameForNum( edict->s.modelindex, current_morph );
	
	if ( morph_name )
	{
		Event *new_event;
		
		new_event = new Event ( EV_Unmorph );
		new_event->AddString( morph_name );
		ProcessEvent( new_event );
	}
}

void Viewthing::SetFrame( Event *ev )
{
	int newframe;
	
	newframe = ev->GetInteger(1);
	if ( newframe < animate->NumFrames() )
	{
		animate->SetFrame( newframe );
		animstate = 0;
		UpdateCvars();
	}
}
