#include maps\mp\_utility;
#include maps\mp\gametypes\_hud_util;
#include common_scripts\utility;
#include maps\mp\agents\_agent_utility;
//#include maps\mp\bots\_bots_strategy;
#include maps\mp\agents\_scriptedAgents;
#include maps\mp\_vl_base;

LUI_MODE_PRELOBBY = 0;
LUI_MODE_LOBBY = 1;
LUI_MODE_CAC = 2;
LUI_MODE_CAO = 3;
LUI_MODE_OBSCURED = 4; // any menu with background obscuring virtual lobby
LUI_MODE_CLANPROFILE = 5;

OBSTACLE_RADIUS = 16;	// 20
OBSTACLE_PATHBIAS = 5;	// 10

delayed_luinotifyserver( delay, channel, value )
{
	wait delay;
	self notify( "luinotifyserver", channel, value );
}


setup_camparams()
{
	// Setup the camParams struct which holds all of our camera state and consts
	camParams = SpawnStruct();
	camParams.TARGETZOFF = 40;		// added to target ent's z to get the camera lookat target
	camParams.BASEDELTA = 0.25;		// Desired distance absolute speed (note we change the desired distance, since pos is determined by the angle)
	camParams.tgtDelta = camParams.BASEDELTA;	// current signed distance target speed (+- BASEDELTA)
	camParams.ACCEL = 0.01;			// acceleration used to reach tgtDelta
	camParams.delta = 0;			// current speed applied to distance
	camParams.BASEDANGLE = 0.5;		// Desired angle absolute speed
	camParams.ANGLEACCEL = 0.01;	// acceleration used to reach tgtDAngle
	camParams.tgtDAngle = camParams.BASEDANGLE;	// current signed angle target speed (+- BASEDANGLE)
	camParams.dAngle = 0;			// current delta angle
	camParams.MAXORBITANGLE = 60;	// MAX +- angle from front of character in orbit mode`
	camParams.MINORBITDIST = 60;	// MIN dist from character in orbit mode
	camParams.MAXORBITDIST = 120;	// MAX dist from character in orbit mode
	camParams.angle = 0;			// current angle
	camParams.dist = 120;			// current distance
	camParams.DOF_Time = 12;			// speed to transition to new setting
	camParams.face_dof_mod = -10;	// how many inches away from head bone to focus
	camParams.CAOtoLobbyFrameDelay = 3;
	camParams.CAOtoLobbyFrameTimer = 0;
	
	

	/// for group style lobby level
	camParams.GAMELOBBYGROUP_CAMOFFSET_ANGLE_RATIO = 0.0;	// angle of game lobby camera - ratio of right vector added to camera position ( centered, positive is to right )
	camParams.GAMELOBBYGROUP_CAMERAZOFF_ZOOM = 45;   		// game lobby camera z offset
	camParams.GAMELOBBYGROUP_TARGETZOFF_ZOOM = 50;    		// game lobby camera target z offset
	camParams.GAMELOBBYGROUP_CAMERA_OFFSET = 6;    			// game lobby camera side offset
	camParams.GAMELOBBYGROUP_CAMERA_TARGETOFFSET = 6;   	// game lobby camera target side offset
	camParams.GAMELOBBYGROUP_CAMERA_UpCloseZ = 513;
	camParams.GAMELOBBYGROUP_CAMERA_NormalZ = 507;
	camParams.GAMELOBBYGROUP_CAMERA_CrouchZ = 483;
	camParams.GAMELOBBYGROUP_CAMERA_HunchZ = 502;	
	camParams.GAMELOBBYGROUP_CAMERA_CloseDistance = 71.5;
	camParams.GAMELOBBYGROUP_CAMERA_NormalDistance = 96.8;
	camParams.GAMELOBBYGROUP_CAMERA_CrouchDistance = 96.8;
	camParams.GAMELOBBYGROUP_CAMERA_HunchDistance = 96.8;
	camParams.GAMELOBBYGROUP_CAMERA_CrouchThreshold = 490;
	camParams.GAMELOBBYGROUP_CAMERA_HunchThreshold = 504.5;
	camParams.GAMELOBBYGROUP_CAMERA_NormalThreshold = 509.5;
	camParams.GAMELOBBYGROUP_MOVESPEED_MODIFIER = 0.95;
	

	
	// for cac
	camParams.CAC_CAMOFFSET_ANGLE_RATIO = 0.0;				// angle of cac camera - ratio of right vector added to camera position ( mostly to the right )
	camParams.CAC_CAMERAZOFF_ZOOM = 7;   					// cac camera z offset
	camParams.CAC_TARGETZOFF_ZOOM = 13;   			 		// cac camera target z offset
	camParams.CAC_CAMERA_OFFSET = 0;    					// cac camera side offset
	camParams.CAC_CAMERA_TARGETOFFSET = 0;  				// cac camera target side offset
	camParams.CAMOFFSET_RATIO_CAC = 0.1;					// ratio of right vector added to camera position ( mostly to the right )
	camParams.CAC_RATIO_ZOOM = 0.2;
	camParams.CAC_FRAMEDELAY = 0;
	camParams.CAC_CAMERAZOFF = 17; 							// cac camera z offset
	camParams.CAC_TARGETZOFF = 14.5;		   			 		// cac camera target z offset
	camParams.CAC_DIST = 69;
	camParams.CAC_WEAP_OFFSET = 8;
	camParams.CAC_WEAP_TUNING = false;
	camParams.CAC_WEAP_TUNING_CAMOFFSET = 0;
	camParams.CAC_WEAP_TUNING_WEAP_SIDEOFFSET = 0;
	camParams.CAC_WEAP_TUNING_WEAP_ZOFFSET = 0;
	camParams.CACWeaponDelayTime = 0.35;		// delays for coming out of CAC and avatar weapon swapping so it looks cleaner
	camParams.CACWeaponAnimDelayTime = 0.1;		
	camParams.CACWeaponAttachDelayTime = 0.05;
	//camParams.cac_weapon_select = false; // currently unused, was used to offset camera
	
	/#
	SetDevDvarIfUninitialized( "scr_vl_cac_weap_tuning_enabled", 0 );
	SetDevDvarIfUninitialized( "scr_vl_cac_weap_tuning_sideoffset", 0 ); // this is for shifting the weapon before it is linked to weaponpedestal, must respawn weapon and pedestal to see changes, back out of menu then back in
	SetDevDvarIfUninitialized( "scr_vl_cac_weap_tuning_weaponPedestal_x", 0 );
	SetDevDvarIfUninitialized( "scr_vl_cac_weap_tuning_weaponPedestal_y", 0 );
	SetDevDvarIfUninitialized( "scr_vl_cac_weap_tuning_weaponPedestal_z", 0 );
	SetDevDvarIfUninitialized( "scr_vl_cac_weap_tuning_MoveModifier", 40 );
	SetDevDvarIfUninitialized( "scr_vl_cac_weap_tuning_Ratio", 0.05 );
	SetDevDvarIfUninitialized( "scr_vl_anim_choose_anim_enable", 0 );
	SetDevDvarIfUninitialized( "scr_vl_anim_index", 0 );
	#/
	
	/// for horse shoe style lobby level
	camParams.GAMELOBBY_MOVESPEED = 150;
	camParams.GAMELOBBY_CAMOFFSET_ANGLE_RATIO = 0.2;		// angle of game lobby camera - ratio of right vector added to camera position ( mostly to the right )
	camParams.GAMELOBBY_CAMERAZOFF_ZOOM = 45;         		// game lobby camera z offset
	camParams.GAMELOBBY_TARGETZOFF_ZOOM = 55;				// game lobby camera target z offset
	camParams.GAMELOBBY_CAMERA_OFFSET = 16;					// game lobby camera side offset
	camParams.GAMELOBBY_CAMERA_TARGETOFFSET = 24;			// game lobby camera target side offset
	camParams.GAMELOBBY_CAMERA_ROTATION_SPEED = 0.5;
	camParams.goal = "avatar";
	
	camParams.GAMELOBBY_CAMERA_CURVE_MODIFY = 0.005;
	camParams.GAMELOBBY_CAMERA_CURVE_STOREDY = 0;	
	camParams.GAMELOBBY_CAMERA_CURVE_MOVEX = 64;
	camParams.GAMELOBBY_CAMERA_DEPTH_SCALER = 0;	
	
	
	camParams.tgt_camoffset_ratio = camParams.CAMOFFSET_RATIO_PRELOBBY;
	camParams.cur_camoffset_ratio = camParams.tgt_camoffset_ratio;
	camParams.cac_camoffset_ratio = 0;
	camParams.cac_weap_loot_offset = ( -0.02, 0, -0.08 );
	camParams.cac_weap_screen_offset = ( 0.05, 0, -0.06 );
	
	camParams.camoffset_ratio_maxspeed = 0.25;
	camParams.CAMZOFF = 10; // 40	// the z applied to the offset from the target to the desired camera position
	camParams.MOVESPEED = 150;		// units per second
	camParams.ANGSPEED = 800;		// degrees per second
	camParams.prelobbyZoom = 0;		// 0,1,2 for zoom distance
	rightStick = self GetUnNormalizedCameraMovement();
	camParams.oldRotX = rightStick[0];
	camParams.oldRotY = rightStick[1];
	camParams.zoom = 0.5;
	camParams.rotate = 0.5;
	
	camParams.framedelay = 0;
	camParams.pre_lobby_framedelay = 4;    // delay for when loading lobby or reloading lobby after despawning your avatar so camera doesn't go crazy
	camParams.delayed_cut = false;
	
	
	// SetDepthOfField( 0, 35, 55, 100, 7.5, 3.5); <-- (old values)
	// SetDepthOfField( 0, 0, 159.8, 205.59, 7.5, 2); <-- (new values)
	camParams.dof_near_start = 0;
	camParams.dof_near_end = 0;
	camParams.dof_far_start = 159.8;
	camParams.dof_far_end = 205.59;
	camParams.dof_near_blur = 7.5;
	camParams.dof_far_blur = 2;
	
	
	
	camParams.origin_offset = (0,0,40);
	
	camParams.PRELOBBY_MOVESPEED = 300;
	camParams.PRELOBBY_TARGETZOFF_ZOOM = 10;
	camParams.PRELOBBY_TARGETZOFF = 42;
	camParams.PRELOBBY_NEARDIST = 40;
	camParams.PRELOBBY_FARDIST = 200;
	camParams.PRELOBBY_CAMERAZOFF = 0;
	camParams.PRELOBBY_CAMERAZOFF_ZOOM = 20;
	camParams.PRELOBBY_CAMERA_RATIO = 0.325;
	camParams.PRELOBBY_RATIO_ZOOM = 0.2;
	camParams.CAMOFFSET_RATIO_LOBBY = -0.1;		// ratio of right vector added to camera position for lobby ( slightly left of center )
	camParams.CAMOFFSET_RATIO_PRELOBBY = 0.19;	// ratio of right vector added to camera position ( mostly to the right )a
	camParams.CAMOFFSET_RATIO_PRELOBBY_LOOT = -1; 
	camParams.CH_CYL_ZOFF_FAR = 36;
	camParams.CH_CYL_ZOFF_NEAR = 42;
	camParams.transitioning = false;
	
	
	camParams.CAO_TARGETZOFF_ZOOM = 10;
	camParams.CAO_TARGETZOFF = 31;
	camParams.CAO_NEARDIST = 40;
	camParams.CAO_FARDIST = 200;
	camParams.CAO_CAMERAZOFF = 0;
	camParams.CAO_CAMERAZOFF_ZOOM = 20;
	camParams.CAO_CAMERA_RATIO = 0.77;
	camParams.CAO_RATIO_ZOOM = 0.2;
	camParams.CAO_CAMOFFSET_RATIO = 0.25;	// ratio of right vector added to camera position ( mostly to the right )a

	mode = GetDvarInt( "virtualLobbyMode", 0 );
	if ( mode == LUI_MODE_PRELOBBY )
	{
		camParams.mode = "game_lobby"; // getting rid prelobby, we just need gamelobby for avatars nows
		start_prelobby_anims();
		SetDVar( "virtualLobbyReady", "0" );	// We'll ensure it is false to start with
	}
	else
	{
		camParams.mode = "transition";		// orbit, transition, prelobby, startmenu
		camParams.tgt_camoffset_ratio = camParams.CAMOFFSET_RATIO_LOBBY;
		camParams.cur_camoffset_ratio = camParams.tgt_camoffset_ratio;
		start_lobby_anims();
		if ( GetDVarInt( "virtualLobbyReady", 0 ) == 0 )
		{
			SetDVar( "virtualLobbyReady", "1" );
		}
		if ( mode == LUI_MODE_CAC )
			self thread delayed_luinotifyserver( 0.1, "cac", 0 );
		else if ( mode == LUI_MODE_CAO )
			self thread delayed_luinotifyserver( 0.1, "cao", 0 );
	}
	camParams.newmode = camParams.mode;
	

	level.camParams = camParams;	// so notifies can change it
}

update_camera_params_ratio( camParams )
{
	if (isdefined(self.cut))
		camParams.cur_camoffset_ratio = camParams.tgt_camoffset_ratio;
	else
	{
		dRatio = camParams.tgt_camoffset_ratio - camParams.cur_camoffset_ratio;
		if ( dRatio < -1*camParams.camoffset_ratio_maxspeed )
			dRatio = -1*camParams.camoffset_ratio_maxspeed;
		else if ( dRatio > camParams.camoffset_ratio_maxspeed )
			dRatio = camParams.camoffset_ratio_maxspeed;
		camParams.cur_camoffset_ratio += dRatio;
	}
}

vlobby_lighting_setup()
{
	player = self;
	if ( IsDefined( level.vl_lighting_setup ))
		player [[ level.vl_lighting_setup ]] ();
	else
	{
		player EnablePhysicalDepthOfFieldScripting();
		player SetPhysicalDepthOfField( 0.613159, 89.8318, level.camParams.DOF_Time, level.camParams.DOF_Time );
	}
}
Set_Avatar_DOF()
{
	if(isdefined(level.camParams.goal))
	{
		if(level.camParams.goal == "moving" || level.camParams.goal == "finding_new_position" || level.vlavatars[level.vl_focus] != level.vlavatars[level.old_vl_focus])
		{
			return; // dont update dof
		}
	}
	if(isdefined(level.camParams.camera.cut) && level.camParams.camera.cut == true)
	{
		return; // dont update dof
	}
	player = self;
	
	// gettin new camera position instead of old counter rotated camera position
	if(IsDefined(level.camParams.camera.goal_location))
	{
		CamLocation = level.camParams.camera.goal_location;
	}
	else
	{
		avatar = level.vlavatars[level.old_vl_focus];
		tag_camera_loc_vec3 = avatar GetTagOrigin("TAG_STOWED_BACK");
		DistTagToAvatar = Distance2D(tag_camera_loc_vec3, avatar.avatar_spawnpoint.origin);
		CamLocationFloor = avatar.avatar_spawnpoint.origin + (AnglesToForward(avatar.avatar_spawnpoint.angles) * DistTagToAvatar);
		CamLocation = (CamLocationFloor[0], CamLocationFloor[1], tag_camera_loc_vec3[2]);
	}
	
	bCamSHouldUpdate = CheckCamPosition(level.camParams.camera, CamLocation, 1.5);
	
	// find out if we should calculate dof dist from where the camera will be, or where the camera is
	if(bCamSHouldUpdate == true)
	{
		predicted_cam_location = CamLocation;
	}
	else
	{
		predicted_cam_location = level.camParams.camera.origin;
	}
	
	
	
	Cam2TgtDist = distance(  level.vlavatars[level.old_vl_focus] GetTagOrigin("j_spine4"), predicted_cam_location);
	player vlobby_dof_based_on_focus(Cam2TgtDist + level.camParams.face_dof_mod);
}
vlobby_dof_based_on_focus(Dist)
{
	if ( Dist <= 0.0 )
		return;
	
	player = self;
	dof_time = level.camParams.DOF_Time;
	
	if (IsDefined(level.vl_dof_based_on_focus))
		player [[level.vl_dof_based_on_focus]](Dist);
	else
	{
	
		///// attempts to set the DOF to keep the background blurry and the weapon in focus even when weapon distance from camera moves
		/// set defaut values based on Hbra3 with f of 2.0 for it's based distance of level.camParams.CAC_DIST default value
		
		Dist2Tgt = Dist;
		player = self;
		FStopPerUnit = 0.025;
		scaler = 0.65;  
		fStopBase = 0.613159; //2.0; // base default fstop to be scaled a distance of 72?
		BaseDistance = 94.9504;  //104
		
		result = ((BaseDistance - Dist2Tgt) * FStopPerUnit);
		fStop = fStopBase + result + (result * scaler);
	
		if(fStop < 0.125)
		{
			fStop = 0.125;	
		}
		else if(fStop > 128)
		{
			fStop = 128;	
		}
		player SetPhysicalDepthOfField( fStop, Dist2Tgt, level.camParams.DOF_Time, level.camParams.DOF_Time * 2 );
	}
}

vlobby_handle_mode_change( mode, newmode, camParams )
{
	player = self;
	if (isDefined( level.vl_handle_mode_change ))
		player [[level.vl_handle_mode_change]]( mode, newmode, camParams );
	else
	{
		
		//////////////////////////////////////////////////////////////////////////////////////////////////////////
		////////// if the mode we are leaving was the mode below, check to see if we should change any post fx.  
		/// 		mode is the current mode we are in not the mode we are going to be in after this function runs.  newmode is
		/// 		is the mode we are transitioning to.
		/// 
		
		if ( mode == "cac" )
		{
			player SetDefaultPostFX();
		}
		else if ( mode == "cao" )
		{
		}
		
		////
		////
		///////////////////////////////////////////////////////////////////////////////////////////////////////////
		
		
		
		
		
		/////////////////////////////////////////////////////////////////////////////////////////////////////
		///////   This is for the newmode that we are going to.  Checks newmode and then changes post FX depending on what newmode we want to go to.
		/// 		This will ovewrite any settings that get set above if the newmode changes the same post fx here. 
		/// 
		
		if (newmode == "cac")
		{
			player VisionSetNakedForPlayer("mp_vlobby_refraction_cac", 0);
			player LightSetForPlayer("mp_vl_create_a_class");
		}
		else if ( newmode == "cao" )
		{
			player SetPhysicalDepthOfField( 1.53, 130, level.camParams.DOF_Time, level.camParams.DOF_Time );
		}
		else if ( newmode == "prelobby" )
		{
			player SetDefaultPostFX();
			
		}
		else if (mode == "prelobby_members")
		{	// shouldn't be used right now.
		}
		else if (mode == "prelobby_loadout")
		{	// shouldn't be used right now.
		}
		else if (mode == "prelobby_loot")
		{	// shouldn't be used right now.
		}
		else if (newmode == "game_lobby")
		{
			player SetDefaultPostFX();
			// player Set_Avatar_DOF();

		}
		else if (mode == "startmenu")
		{	// shouldn't be used right now.
		}
		else if (mode == "transition")
		{	// shouldn't be used right now.
		}
		
		////
		////
		///////////////////////////////////////////////////////////////////////////////////////////////////////////
	}
}

SetDefaultPostFX()
{
	player = self;
	player VisionSetNakedForPlayer("mp_vlobby_room", 0);
	player LightSetForPlayer("mp_vlobby_room");
}

SetDefaultDOF()
{
	///////////////////////////////////////////////
	//////////////  vl_dof_based_on_focus() should overide DOF for each camera position when looking at lobby avatars/characters
	player = self;
	player SetPhysicalDepthOfField( 0.613159, 89.8318, level.camParams.DOF_Time, level.camParams.DOF_Time );
}

fixlocalfocus()
{
	if (!isdefined( level.vlavatars[level.vl_focus] ) )
	{
		foreach ( idx, avatar in level.vlavatars )
		{
			level.vl_focus = idx;
			break;
		}
	}
	if ( !isdefined( level.vlavatars[level.vl_local_focus] ) )
		level.vl_local_focus = level.vl_focus;
}

vlobby_player()
{
	self endon("disconnect");
	
	wait 0.05;
	
	player = self;
	
	player SetClientOmnvar( "ui_vlobby_round_state", 0 );			
	player SetClientOmnvar( "ui_vlobby_round_timer", 0 );
	
	
	
	player grab_players_classes();	// we grab whenever we start the vl, since we may be coming back from a game straight into the original lobby
	player_origin = player.origin;
	player_angles = player.angles;
	
	target_origin = player_origin - ( 0, 0, 30 );
	player setup_camparams();
	player vlobby_lighting_setup();
	
	
	facedir = AnglesToForward( player.angles );
	rightdir = AnglesToRight( player.angles );
	movedir = facedir;
	ground_origin = GetGroundPosition( player_origin, 20, 512, 120 );
	xuid = player getxuid();
	localPlayMatchReturn = ( xuid == "" );	
	targetAvatar = undefined;
	level.needLocalMemberId = true;
	level.vl_focus = 0;
	level.vl_local_focus = 0;
	player.team = "spectator"; // 
	// ownerId for the avatar the camera focus's on.
	
	
	primary_weapon = "iw5_hbra3_mp";
	primWeap = "iw5_hbra3";
	if(isdefined(player.loadouts["customClasses"][0]["primary"]) && player.loadouts["customClasses"][0]["primary"] != "none")
	{
		// this should get us the FULL weapon name with attachments so we should spawn with the correct weapon now and not have to change moments after spawn		
		
		loadout_weapon = player.loadouts["customClasses"][0]["primary"];
		
		loadout_attachment1 = player.loadouts["customClasses"][0]["primaryAttachment1"];
	  	loadout_attachment2 = player.loadouts["customClasses"][0]["primaryAttachment2"]; 
	  	loadout_attachment3 = player.loadouts["customClasses"][0]["primaryAttachment3"]; 
	  	loadout_camo = player.loadouts["customClasses"][0]["primaryCamo"];
	  	loadout_reticule = player.loadouts["customClasses"][0]["primaryReticule"];
	  	
	  	if( IsDefined(loadout_camo) )
	  	{
	  		loadout_camo = int(tableLookup( "mp/camoTable.csv", 1, loadout_camo, 0 ));
	  	}
	  	else
	  	{
	  		loadout_camo = undefined;
	  	}
	  	if( IsDefined(loadout_reticule) )
	  	{
	  		loadout_reticule = int(tableLookup( "mp/reticleTable.csv", 1, loadout_reticule, 0 ));
	  	}
	  	else
	  	{
	  		loadout_reticule = undefined;
	  	}
		
		primary_weapon = maps\mp\gametypes\_class::BuildWeaponName( loadout_weapon, loadout_attachment1, loadout_attachment2, loadout_attachment3, loadout_camo, loadout_reticule );
		primWeap = player.loadouts["customClasses"][0]["primary"];
	}
	else if(isdefined(player.primaryWeapon)) // just incase
	{
		primary_weapon = player.primaryWeapon;
		primWeap = player.pers["primaryWeapon"];
	}
	
	cao_spawnpoint = getent("cao_spawnpoint", "targetname");
	if ( !localPlayMatchReturn )
	{
		vlprintln( "adding xuid " + xuid + "from vlobby_player" );
		ownerId = add_avatar( xuid );
		assert( ownerId == 0 );
		player spawn_an_avatar( player.avatar_spawnpoint, primary_weapon, player.secondaryWeapon, primWeap, player.loadoutEquipment, player.loadoutOffhand, player.perks, player.sessionCostume, player.name, ownerId, false );
		setEntPlayerXuidForEmblem( level.vlavatars[ownerid], xuid );
		thread SetVirtualLobbyPresentable();
	}
	else
	{
		level.needToPresent = true;		// flag that the next spawn we will need to present for local play returning from a match
	}
	SetDvar( "virtuallobbymembers", level.xuid2ownerId.size );
	
	// for special cao agent
	player.cao_agent = player spawn_an_avatar( cao_spawnpoint, primary_weapon, player.secondaryWeapon, primWeap, player.loadoutEquipment, player.loadoutOffhand, player.perks, player.sessionCostume, player.name, 0, true );
	setEntPlayerXuidForEmblem( player.cao_agent, xuid );
	hide_avatar( player.cao_agent  );
	
	player thread monitor_class_select_or_weapon_change( 0 );
	
	if ( !localPlayMatchReturn )
	{
		targetAvatar = level.vlavatars[level.vl_focus];
		targetAvatar.membertimeout = GetTime();
		targetAvatar.currentSelectedClass = player.currentSelectedClass;
		targetAvatar.player = player;
		targetAvatar.sessionCostume = targetAvatar.costume;	// ensure we keep the same randomly picked costume if we have one.
		virtual_lobby_set_class( 0, "lobby" + (targetAvatar.currentSelectedClass+1), true );
	}
	
	thread monitor_debug_addfakemembers( player, level.camParams );

	origin = (-70.7675, -691.293, 507.472); //hardcoding the camera start point to a beter location
	toavatar = player.avatar_spawnpoint.origin - origin;
	angles = (0,87,0); // hardcoding the start camera angles to better angles to better match the normal camera
	toavatar = VectorNormalize( toavatar );
	
	camera = spawn( "script_model", origin );
	camera.angles = angles;
	camera SetModel( "tag_player" );
	camera.startorigin = origin;
	camera.startangles = angles;
	camera.savedorigin = (0,0,0);
	camera.cam_percent_away = 0;
	camera.HasReachedFinalPos = true;
	camera.CamDirectionForward = true;
	camera.cut = true;
	camera.Gap = false;
	camera.finished = true;
	camera.goal_location = origin;
	self StoreCameraTargets(camera);
	sky_dist_from_cam = 400;
	player.camera = camera;
	camera.player = player;
	player.HasTouchedStick = false;
	weaponAngles = (0,90,0);
	camera.MovingState = "starting";

	level.vlCamera = camera;

	player SetOrigin(camera.origin);
	player PlayerLinkTo( camera, "tag_player" );
	player CameraLinkTo( camera, "tag_player" );
	level.in_firingrange = false;
	
	player AllowFire( false );
	player prep_for_controls( player.spawned_avatar, player.spawned_avatar.spawn_angles );
	player prep_for_controls( player.cao_agent, player.cao_agent.spawn_angles );
	
	updateSessionState("spectator");

	camParams = level.camParams;
	camParams.camera = camera;
	init_path_constants( camera );
	bg_dist_from_cam = 300;
	level.old_vl_focus = level.vl_focus;

	sky_dist_from_cam = 1000;
	camForward = AnglesToForward( camera.angles );
	bg_plane = undefined;
	bg_plane_static = undefined;
	bg_plane_angles = undefined;
	bg_plane_offset = undefined;
	level.camParams.framedelay = level.camParams.pre_lobby_framedelay; // everytime we start the lobby for the first time small delay before we find the avatar camera 
	
	thread monitor_player_removed();
	thread monitor_vl_mode_change();

	fly = false;
	testpaths = false;
	
	player SetClientDVar( "cg_fovscale", "0.6153" );	// using this to flag first entry into vlobby
	
	player notify("fade_in");
	
	while (1)
	{
		avatar_after_spawn(); // we check is weapon loaded now before revealing the lobby, if not loaded we need to keep checking. 
		if ( level.vlavatars.size == 0 )
		{
			if ( camParams.mode == "game_lobby" || camParams.mode == "prelobby_loot" )
			{
				hide_avatar( player.cao_agent  );
				camera.cut = true; // cut to first camera location when lobby loaded
				lobby_update_group_new( camera, undefined, camParams, player.avatar_spawnpoint ); // go to lobby_update_group_new istead of prelobby	
				vlobby_vegnette(1, "ac130_overlay_pip_vignette_vlobby");
			}
			wait 0.05;
			continue;
		}
		targetAvatar = level.vlavatars[level.vl_focus];
		if (testpaths)
		{
			testpaths = false;
			player thread debug_pathing();
		}
		if ( GetDvarInt("scr_vl_debugfly" ) > 0 )
		{
			if (!fly)
			{
				fly = true;
				/#
				SetDvar( "r_dof_enable", "0" );
				#/
				SetDvar( "lui_enabled", "0" );
				level.debug_fly = undefined;
				player AllowFire( true );
			}
			player debug_fly( camera );
		}
		else if ( fly )
		{
			/#
			SetDvar( "r_dof_enable", "1" );
			#/
			SetDvar( "lui_enabled", "1" );
			fly = false;
			camera.origin = camera.startorigin;
			camera.angles = camera.startangles;
			player AllowFire( false );
		}
		if ( !level.in_firingrange )
		{
			if ( level.vl_focus != level.old_vl_focus )
			{
				player prep_for_controls( level.vlavatars[level.vl_focus], level.vlavatars[level.vl_focus].angles); // this was missing angles and would pass in undefined angles before
				// no longer need to set anything for old focus because the camera cut works to player avatar looks clean now.
				if(camParams.mode != "game_lobby") // do we need this block of code to set the mode at all?
				{
					camParams.newmode = "transition";
				}
				level.old_vl_focus = level.vl_focus;
				camParams.transitioning = true;
			}
			if ( isdefined(level.vl_cao_focus) || isdefined(level.prv_vl_cao_focus) )
			{
				if (!isdefined( level.vl_cao_focus ))
				{
					camParams.newmode = camParams.pushmode;
					level.prv_vl_cao_focus = undefined;
				}
				else if (!isdefined( level.prv_vl_cao_focus ))
				{
					camParams.newmode = "cao";
					camParams.pushmode = camParams.mode;
					level.prv_vl_cao_focus = level.vl_cao_focus;
				}
				else if (level.vl_cao_focus != level.prv_vl_cao_focus)
				{	// switched which part we're looking at
					level.prv_vl_cao_focus = level.vl_cao_focus;
				}
			}
			if ( isdefined( level.cac ) || isDefined( level.prv_cac ) )
			{
				if (!isdefined( level.cac ) )
				{
					camParams.newmode = camParams.pushmode;
					level.prv_cac = undefined;
					level.prv_cac_weapon = "none";
				}
				else if ( !isdefined( level.prv_cac ) )
				{
			//		hide_avatars();	// This makes the avatars pop coming back from CAC get rid of it From Evan
					camParams.newmode = "cac";
					camParams.pushmode = camParams.mode;
					level.prv_cac = level.cac;
				}
			}
			if ( level.vlavatars.size == 0 )
				continue;
			fixlocalfocus();
			targetAvatar = level.vlavatars[level.vl_focus];
			if (camParams.newmode != camParams.mode)
			{
				player vlobby_handle_mode_change( camParams.mode, camParams.newmode, camParams );
				
				if (camParams.newmode == "cac")
				{
					hide_avatar( player.cao_agent  );
					player.HasTouchedStick = false;
					camParams.mode = camParams.newmode;
					camParams.tgt_camoffset_ratio = camParams.CAC_CAMOFFSET_ANGLE_RATIO;
					//camParams.cac_weapon_select = false;
						
				//	camera.cut = true; no reason to cut to CAC nothing in 3d space to cut to. 
					targetAvatar = level.vlavatars[level.vl_focus];
					player prep_for_controls(targetAvatar, targetAvatar.angles); // save stick and avatar angles when going into CAC
				}
				else if (camParams.mode == "cac") // if mode was CAC and now new mode is not, so we know that we are leaving CAC
				{	// cut if we exit cac as well
					//camParams.cac_weapon_select = false;
					show_non_owner_avatars();
					targetAvatar = level.vlavatars[level.vl_focus];
				//	camera.cut = true; // we will now only cut from CAC to lobby if focus not the player avatar
					player.HasTouchedStick = false;
				}
				if (camParams.newmode == "cao")
				{
					if ( IsDefined(level.vl_local_focus) && IsDefined( level.vlavatars[level.vl_local_focus] ) )
					{
						agent = level.vlavatars[level.vl_local_focus];
						level.players[0].costume = agent.costume;
						costumeFound = level.players[0] maps\mp\gametypes\_teams::playerCostume();
						assert( costumeFound );
						costumeFound = agent maps\mp\gametypes\_teams::playerCostume();
						assert( costumeFound );
						
						// ensure CaO agent is in synch for whenever we go back to CaO
						caoAgent = level.players[0].cao_agent;
						vl_avatar_costume( caoAgent, agent.costume );
						costumeFound = caoAgent maps\mp\gametypes\_teams::playerCostume();
						assert( costumeFound );		
						// ensure the emblems for the correct player are associated with this actor
						// need the xuid for the focus
						foreach ( xuid, idx in level.xuid2ownerId )
						{
							if ( idx == level.vl_local_focus )
							{
								if ( isDefined( level.cao_xuid ) && level.cao_xuid != xuid )
									SetEntPlayerXuidForEmblem( caoAgent, level.cao_xuid, true );	// clear previous cao association
								SetEntPlayerXuidForEmblem( caoAgent, xuid );
								level.cao_xuid = xuid;
								break;
					}
						}
					}
					

					show_avatar( player.cao_agent  );
					player.cao_agent HackAgentAngles(player.cao_agent.spawn_angles);
					hide_avatars();
					targetAvatar = player.cao_agent;
					player prep_for_controls(player.cao_agent, player.cao_agent.angles);
					start_cao_anims();
					camParams.mode = camParams.newmode;
					
					camera.cut = true;
					player SetOrigin(targetAvatar.origin);
				}
				else if ( camParams.newmode == "clanprofile" )
				{	
					hide_avatar( player.cao_agent  );
					show_non_owner_avatars();
					targetAvatar = level.vlavatars[level.vl_focus];
					start_lobby_anims();
					player prep_for_controls(targetAvatar, targetAvatar.angles);
				}
				/* // we are not using prelobby anymore
				else if (camParams.newmode == "prelobby")
				{
					
				}
				*/
				else if (camParams.newmode == "prelobby_loot")
				{
					camera.cut = true;
				}
				else if (camParams.newmode == "transition")
				{ 
					// if its "transition" just go to game_lobby
				//	if(camParams.mode == "prelobby")
				//	{
						camParams.newmode = "game_lobby"; 
				//	}
				}
				if (camParams.newmode == "game_lobby")
				{
					fixlocalfocus(); // was needed when prelobby happned first
				//	hide_avatar( player.cao_agent  ); // was needed when prelobby happened first
					vl_focus = 0;
					foreach ( idx, avatar in level.vlavatars )
					{
						vl_focus = idx;
						break;
					}
					// From Evan
					if(camParams.mode == "cao" || camParams.mode == "cac" )
					{
						camera.finished = true;
						if ( IsDefined( level.vl_cao_focus ) )
						{
							level.vl_local_focus = vl_focus;
							vl_focus = GetFocusFromController( level.vl_cao_focus );
						}
						else 
						{
							level.vl_local_focus = vl_focus;
						}
					}
					
					// sometimes it goes directly to your avatar like from CAO or CAC but we don't want to see the old focus spin for a moment
					// could be artifact from going to pre_lobby for a frame after CAC though... 
					if(isdefined(level.vlavatars) && isdefined(level.old_vl_focus) && isdefined(level.vlavatars[level.old_vl_focus]))
					{
						player prep_for_controls(level.vlavatars[level.old_vl_focus], level.vlavatars[level.old_vl_focus].angles);
					}
					// From Evan End

					camParams.goal = "waiting";
					
					if(camParams.mode != "cac" ) // using show here can break avatar weapon show/hide state when we are just coming out of CAC
					{
						show_avatars();
					}
					level.vl_focus = vl_focus;
					level.old_vl_focus = vl_focus;
					targetAvatar = level.vlavatars[vl_focus];
					
					if(camParams.mode != "cac")
					{
						camera.cut = true; // always cut if we were not in CAC
					}
					// this lets us know if we were someplace else in the lobby instead of our avatar even if old focus got overwritten and
					// this lets us avoid cutting the camera movement if we are already on player focus.  If player is already focus camera cut when the weapon anim plays can look bad
					else if((camParams.mode =="cac") && isdefined(camera.avatar_spawnpoint) && (camera.avatar_spawnpoint != targetAvatar.avatar_spawnpoint))
					{
						camera.cut = true;
					}
						
					start_lobby_anims();
					camera.last_avatar_position = targetAvatar.avatar_spawnpoint;
					camera.avatar_spawnpoint = targetAvatar.avatar_spawnpoint;
			
					player prep_for_controls(targetAvatar, targetAvatar.angles);
					
					camera.MovingState = "starting";
					player SetOrigin(targetAvatar.origin);
				}
				camParams.mode = camParams.newmode;
			}
			if (camParams.mode == "startmenu")
			{	// shouldn't be used right now.
				start_menu_update( camera, camParams );
			}
			else if ( camParams.mode == "cao" )
			{
				targetAvatar = player.cao_agent;
				cao_update( camera, targetAvatar, camParams );
				vlobby_vegnette(1, "ac130_overlay_pip_vignette_vlobby_cao");
				
			}
			else if ( camParams.mode == "clanprofile" )
			{
				clan_profile_update( camera, targetAvatar, camParams );
			}
			else if ( camParams.mode == "cac" )
			{
				lobby_update_group_new( camera, level.vlavatars[level.vl_focus], camParams, player.avatar_spawnpoint ); // go ahead and update the camera while in the background of CAC.  This way sometimes it will settle before player gets back from CAC
			}
			// removed legacy prelobby stuff that was here
			else if ( camParams.mode == "transition" )
			{
				/// do transition
			
				camParams.newmode = "game_lobby";
			}
			else
			{	// must be in game lobby
				
				if(camParams.CAOtoLobbyFrameTimer <= 0)
				{
					hide_avatar( player.cao_agent  );
					lobby_update_group_new( camera, targetAvatar, camParams, player.avatar_spawnpoint ); // was missing argument.
					vlobby_vegnette(1, "ac130_overlay_pip_vignette_vlobby");
				}
				else
				{
					camParams.CAOtoLobbyFrameTimer = camParams.CAOtoLobbyFrameTimer - 1;
				}
			}
		}
		if ( level.in_firingrange )
		{
			vlobby_vegnette(0, "ac130_overlay_pip_vignette_vlobby");
		}
		wait 0.05;
	}
}
TableLookupWeaponOffsets(WeaponString,SearchColumn,ReturnColumn)
{
	
	offset = Float(TableLookup( "mp/vlobby_cac_offsets.csv", SearchColumn, WeaponString, ReturnColumn ));
	if(!isdefined(offset))
	{
		offset = 0;
	}
	return offset;
}
// deleted legacy drift stuff

StoreCameraTargets(camera)
{
	PlayerPositions = GetEntArray("player_pos","targetname");
	camera.CamerHelperArray = [];
	foreach(AvatarSpawn in PlayerPositions)
	{
		// filling with garbage data for now
		camera.CamerHelperArray[camera.CamerHelperArray.size] = AvatarSpawn;
	}
	foreach(AvatarSpawn in PlayerPositions)
	{
	//	CameraTarget = GetEnt(AvatarSpawn.targetname,"target"); // reverse find entity.  find who is targgeting me instead of who I target
		CameraTarget = GetEnt(AvatarSpawn.target,"targetname");
		if(CameraTarget.script_noteworthy == "camera_target")
		{
			AvatarSpawn.camera_lookat = CameraTarget;
		}
		CameraEnt = GetEnt(CameraTarget.target,"targetname");
		if(CameraEnt.script_noteworthy == "camera")
		{
			AvatarSpawn.camera_helper = CameraEnt;
			CameraEnt.camera_goal = AvatarSpawn;
		
			CameraEnt.camera_lookat = AvatarSpawn.camera_lookat;
		}
		if(AvatarSpawn.script_noteworthy == "0")
		{
			level.camParams.last_camera_helper = AvatarSpawn.camera_helper;
		}
		Num = int(AvatarSpawn.script_noteworthy);
		{
			camera.CamerHelperArray[Num] = AvatarSpawn;
		}
	}
}

// Given the desired camera position and target position, and ratio,
// calculate the vector that will point the camera so the target appears at the correct location on screen
/*
 * 	if ratio is xonscreen/distcamtoscreen, and d is the distance to a point in the center of the screen that
 * puts our target where we want in X on screen, and t is the distance to the right of our center point to the target,
 * then T/D = ratio and T = D*ratio.  If V is our desired normalized vector the camera will point at and R is a normalized
 * right vector from our lookat point, C is the camera position and T is the target position, then C + d*V + t*R = T.
 * C + d*V + ratio*d*R = T
 * d*V + ratio*d*R = T - C
 * if we ensure our dZ is 0 for now, then we get V = (vx, vy, 0), R = ( vy, -vx, 0 )
 * ( d*vx + ratio*d*vy, d*vy - ratio*d*vx, 0) = T - C = O = (ox, oy, 0)
 * Since we will normalize at the end of all of this, we can ignore the d
 * And, using linear algebra we can solve
*/
calc_target_dir( camerapos, targetpos, camParams )
{		
	c2t = targetpos - camerapos;
	dist2D = Length2D( c2t );
	D = dist2D / sqrt( 1 + camParams.cur_camoffset_ratio*camParams.cur_camoffset_ratio );
	vx = c2t[0] - camParams.cur_camoffset_ratio*c2t[1];
	vy = camParams.cur_camoffset_ratio*c2t[0] + c2t[1];
	v = D * VectorNormalize( (vx, vy, 0) );
	// we could test by adding ratio*( v[1], -v[0], 0 ) and we should get c2t[0..1]
	// now put the Z back
	v = v + ( 0, 0, c2t[2] );
	return v;
}

hacky_get_delta_xrot( camParams, rotX )
{
	dRot = rotX - camParams.oldRotX;
	if (dRot < -180 )
		dRot = 360 + dRot;
	else if (dRot > 180)
		dRot = 360 - dRot;
	camParams.oldRotX = rotX;
	return dRot;
}
hacky_get_delta_yrot( camParams, rotY )
{
	dRot = roty - camParams.oldRotY;
	if (dRot < -180 )
		dRot = -1;
	else if (dRot > 180)
		dRot = 1;
	else
		dRot = 0;
	camParams.oldRotY = rotY;
	return dRot;
}

CamMove( target, time, acctime, dectime )
{
	if (isdefined( self.cut ))
	{
		//self DontInterpolate();
		self.origin = target;
	}
	else
		self moveto( target, time, acctime, dectime );
}

CamRotate( target, time, acctime, dectime )
{
	if (isdefined( self.cut ))
		self.angles = target;
	else
		self rotateto( target, time, acctime, dectime );
}


RotateAvatarTagCamera(avatar, spawnpoint)
{
	if ( !IsDefined( avatar ) )
	{	
// removed pointless code here that was just being overwritten anyway.
		CameraData = SpawnStruct();
		CameraData.camera_tag_origin = ( -70.8, -691.3, 502.2 );
		CameraData.camera_tag_angles = ( 0, 87, 0 );
		return cameraData;
	}
	
	tag_camera_loc_vec3 = avatar GetTagOrigin("TAG_STOWED_BACK");
	tag_camera_angles_vec3 = avatar GetTagAngles("TAG_STOWED_BACK");
	diff_angles_y = avatar.spawn_angles[1] - avatar.angles[1];
	
	up = (0,0,1);	// up is always vertical
	NewPos = tag_camera_loc_vec3 - avatar.origin;
	RotatedPos = RotatePointAroundVector(up, NewPos, diff_angles_y);
	NewCamPos = avatar.origin + RotatedPos;
	
	avatar.camera_tag_origin = NewCamPos;
	avatar.camera_tag_angles = (tag_camera_angles_vec3[0], AngleClamp(tag_camera_angles_vec3[1] + diff_angles_y), tag_camera_angles_vec3[2]);
	
	CameraData = SpawnStruct();
	CameraData.camera_tag_origin = avatar.camera_tag_origin;
	CameraData.camera_tag_angles = avatar.camera_tag_angles;

	return CameraData;	
}

CheckCamPosition(camera, position, units)
{
	if(!IsDefined(units))
	{
		units = 2;
	}
	
	CamDist2Target = distance (position, camera.origin);

	if(CamDist2Target >= units)
	{
		return true;
	}
	else
	{
		return false;
	}
}


//// Game Lobby with group setup
lobby_update_group_new( camera, targetAvatar, camParams, spawnpoint )
{	
	if ( !IsDefined( targetAvatar ) ) // moved prelobby stuff here to handle before avatar spawns 
	{
		// hardcoding startingpoint if no avatar
		level.camParams.framedelay = level.camParams.pre_lobby_framedelay;
		CameraAngles = camera.startangles;
		if(IsDefined(spawnpoint))
		{
			CameraAngles = (0, spawnpoint.angles[1] + 180, 0);
		}
		newCamOrigin = camera.startorigin;
		movedist = Distance( camera.origin, newCamOrigin );
		movetime = movedist / camParams.PRELOBBY_MOVESPEED;
		if (movetime < 0.1)
		{
			movetime = 0.1;
		}
		
		camera CamMove( newCamOrigin, movetime, movetime*0.5, movetime*0.5 );			
		camera CamRotate( CameraAngles, movetime, movetime*0.5, movetime*0.5 );
		
	}
	else // after avatar has spawned use below for camera
	{
		// we know the avatar just spawned. We want to save the stick angles and avatar angles once
		if(level.camParams.framedelay == level.camParams.pre_lobby_framedelay)
		{
			self prep_for_controls(targetAvatar, targetAvatar.angles); 
		}
		
		// hardcoding camera angles to be based on avatar spawnpoint + 180.  Not titlted view then.  less camera movement. 
		CameraAngles = (0,87,0);
		if(IsDefined(targetAvatar.avatar_spawnpoint))
		{
			CameraAngles = (0,targetAvatar.avatar_spawnpoint.angles[1] + 180, 0);
		}

		tag_camera_loc_vec3 = targetAvatar GetTagOrigin("TAG_STOWED_BACK");
		
		// GetAnimData() used to get camera_state. camera_state decides Z and distance values. We take that and then use avatar spawnpoint angles forward * distance to determine XY.
		if(!IsDefined(targetAvatar.camera_state))
		{
			targetAvatar.camera_state = "stand";
		}

		if(targetAvatar.camera_state == "crouch") 
		{
			CamLocationFloor = targetAvatar.avatar_spawnpoint.origin + (AnglesToForward(targetAvatar.avatar_spawnpoint.angles) * camParams.GAMELOBBYGROUP_CAMERA_CrouchDistance);
			CamLocation = (CamLocationFloor[0], CamLocationFloor[1], camParams.GAMELOBBYGROUP_CAMERA_CrouchZ);
		}
		else if(targetAvatar.camera_state == "hunch")
		{
			CamLocationFloor = targetAvatar.avatar_spawnpoint.origin + (AnglesToForward(targetAvatar.avatar_spawnpoint.angles) * camParams.GAMELOBBYGROUP_CAMERA_HunchDistance);
			CamLocation = (CamLocationFloor[0], CamLocationFloor[1], camParams.GAMELOBBYGROUP_CAMERA_HunchZ);
		}
		else if(targetAvatar.camera_state == "stand")
		{
			CamLocationFloor = targetAvatar.avatar_spawnpoint.origin + (AnglesToForward(targetAvatar.avatar_spawnpoint.angles) * camParams.GAMELOBBYGROUP_CAMERA_NormalDistance);
			CamLocation = (CamLocationFloor[0], CamLocationFloor[1], camParams.GAMELOBBYGROUP_CAMERA_NormalZ);
		}
		else if(targetAvatar.camera_state == "zoom_high")
		{
			CamLocationFloor = targetAvatar.avatar_spawnpoint.origin + (AnglesToForward(targetAvatar.avatar_spawnpoint.angles) * camParams.GAMELOBBYGROUP_CAMERA_CloseDistance);
			CamLocation = (CamLocationFloor[0], CamLocationFloor[1], camParams.GAMELOBBYGROUP_CAMERA_UpCloseZ);
		}
		else
		{
			CamLocationFloor = targetAvatar.avatar_spawnpoint.origin + (AnglesToForward(targetAvatar.avatar_spawnpoint.angles) * camParams.GAMELOBBYGROUP_CAMERA_NormalDistance);
			CamLocation = (CamLocationFloor[0], CamLocationFloor[1], camParams.GAMELOBBYGROUP_CAMERA_NormalZ);
		}
		
		camera.goal_location = CamLocation;
		
		// don't update camera until the delay is done.
		bCamSHouldUpdate = false;
		if(level.camParams.framedelay == 0)
		{
			bCamSHouldUpdate = CheckCamPosition(camera,CamLocation, 1.5);
		}
	
		camParams.cur_camoffset_ratio = camParams.GAMELOBBYGROUP_CAMOFFSET_ANGLE_RATIO;
		camParams.tgt_camoffset_ratio = camParams.GAMELOBBYGROUP_CAMOFFSET_ANGLE_RATIO;
		
		camera update_camera_params_ratio( camParams );
		
		AccelTime = 0;
		DecelTime = 0;
	//	movetime = 0.5;
		
		movedist = Distance( camera.origin, CamLocation );
		movetime = movedist / camParams.GAMELOBBY_MOVESPEED;
		if (movetime < 0.1)
			movetime = 0.1;
		
		
		CorrectedMoveTime = movedist / (camParams.GAMELOBBY_MOVESPEED * level.camParams.GAMELOBBYGROUP_MOVESPEED_MODIFIER);
		if (CorrectedMoveTime < 0.1)
			CorrectedMoveTime = 0.1;
		
		rotationtime = 1;
		
		mode = GetDvarInt( "virtualLobbyMode", 0 );
		if ( mode != LUI_MODE_CAC && mode != LUI_MODE_OBSCURED && mode != LUI_MODE_CAO)
		{
			self RightStickRotateAvatar(targetAvatar, 0.5);
		}
		else if(mode == LUI_MODE_OBSCURED || mode == LUI_MODE_CAO)
		{
			self prep_for_controls(targetAvatar, targetAvatar.angles); // handle this at mode switch in vlobby()
			camera.cut = true; // cut if coming out of obscured menu.
		}
		else if(mode == LUI_MODE_CAC)
		{
			self prep_for_controls(targetAvatar, targetAvatar.angles);
		}
			
		
		if(level.camParams.framedelay > 0)
		{
			level.camParams.framedelay = level.camParams.framedelay - 1;
		}
		
		
		
		
		///////////////// FIND OUT HERE IF TARGET AVATAR CHANGE OR ANIM CHANGE
		if(IsDefined(targetAvatar.avatar_spawnpoint) && IsDefined(camera.avatar_spawnpoint) && targetAvatar.avatar_spawnpoint != camera.avatar_spawnpoint)
		{
			camera.LastAvatarPositionEnt = camera.avatar_spawnpoint;
			camera.avatar_spawnpoint = targetAvatar.avatar_spawnpoint;
			camParams.goal = "finding_new_position";
			bCamSHouldUpdate = true;
			camera.finished = false;
		}
		else if(bCamSHouldUpdate == true)
		{
			camParams.goal = "moving";
		}
		
		
		//////////////////////////////////////////////////////////////////////////////////
		if(camParams.goal == "waiting")
		{	
			if(bCamSHouldUpdate == true)
			{
				camParams.goal = "moving";
				camera.finished = true;
			}
		
			camera.last_avatar_position = targetAvatar.avatar_spawnpoint;
		}
		
		
		///////////////// HERE WE FIND OUR NEW GOAL AVATAR POSITION UP OR DOWN THE AVATAR POSITION INDEX THEN GO TO NEXT CONNECTED NODE
		if(camParams.goal == "finding_new_position")
		{	
			if(bCamSHouldUpdate == true)
			{
				camera.LastAvatarPositionEnt = camera.avatar_spawnpoint;
				camera.avatar_spawnpoint = targetAvatar.avatar_spawnpoint;
				
				
				camera.obstacles = [];
				radius = OBSTACLE_RADIUS;
				foreach ( avatar in level.vlavatars )
				{
					obstacle = [];
					obstacle["center"] = avatar.avatar_spawnpoint.origin;
					obstacle["radius"] = radius;
					camera.obstacles[ camera.obstacles.size ] = obstacle;
				}
				
				build_path_info( camera, camParams, camera.origin, CamLocation, CameraAngles ); // using new values
				camera.fparams = calc_f_from_avatar( targetAvatar );
				camera.target_from_avatar = get_target_from_avatar( targetAvatar );
				camParams.goal = "moving";
				
			}
			camParams.goal = "moving";
		}
		
		///////////////// HERE IS WHERE WE ACTUALLY MOVE
		if(camParams.goal == "moving")
		{			
			if(level.camParams.framedelay <= 0)
			{
				level.camParams.framedelay = 0;	
				
				if(camera.finished == false)
				{
					if ( isdefined( camera.cut ) )
					{
						// hardcoding camera angles
						camera CamMove( CamLocation, movetime, AccelTime, DecelTime );
						camera CamRotate( CameraAngles, rotationtime, rotationtime * 0.5, rotationtime * 0.5 ); // using new values
						
						camera.finished = true;
					}
					else
					{
						camera.finished = update_camera_on_path( camera, camParams );
					}
				}
				if (camera.finished)
				{
					camera CamMove( CamLocation, CorrectedMoveTime, CorrectedMoveTime * 0.5, CorrectedMoveTime * 0.5 );
					camera CamRotate( CameraAngles, CorrectedMoveTime, CorrectedMoveTime * 0.5, CorrectedMoveTime * 0.5 ); // using new values
				
					camParams.goal = "waiting";
				}
			}
		}
		level.players[0] Set_Avatar_DOF();	
	}
	
	if ( isdefined(camera.cut) )
	{
		camera DontInterpolate();
		camera.cut = undefined;
	}

	if ( GetDVarInt( "virtualLobbyReady", 0 ) == 0 )
	{
		wait 1.0;	// for some reason the transition is not smooth without delaying a bit the set virtualLobbyReady to 1
		SetDVar( "virtualLobbyReady", "1" );	// using this to flag first entry into vlobby
		thread SetVirtualLobbyPresentable();
	}
}

SetVirtualLobbyPresentable()
{
	level notify("cancel_vlp");
	level endon("cancel_vlp");
	// we want to know if the weapon has been loaded first
	if ((level.vlavatars.size > 0) && IsDefined(level.vlavatars[0]) && IsDefined(level.vlavatars[0].primaryweapon) && IsWeaponLoaded(level.vlavatars[0].primaryweapon)) 
	{
		level.needToPresent = undefined;
		wait 0.5;
		SetDVar( "virtualLobbyPresentable", "1" );	// using this to flag that we can fade out the coverage
	}
	else
	{
		level.needToPresent = true;
	}
}

ResetVirtualLobbyPresentable()
{
	level notify("cancel_vlp");
	level endon("cancel_vlp");
	level.needToPresent = undefined;
	wait 0.25;	// wait same time we wait to remove the last avatar
	SetDVar( "virtualLobbyPresentable", "0" );	// using this to flag that we can fade out the coverage
}

MovePlayerEyeToCam(camera)
{
	//because we need player eye at cam to build screen space with function WorldPointToScreenPos()
	
	player = level.players[0];
	eyePos = player GetEye();
	eyeDif = eyePos - player.origin;
	player SetOrigin(camera.origin - eyeDif, false); // don't teleport or it won't work with WorldPointToScreenPos()
	player SetPlayerAngles((player.angles[0],camera.angles[1],player.angles[2]));
}

GetAspectRatio()
{
	AspectRatioData = SpawnStruct();
	Resolution = GetDvar( "r_mode", "1280x720 [16:9]" );
	Width_X_Height = StrTok(Resolution, " ");
	ResolutionArray = StrTok(Width_X_Height[0], "x");
	AspectRatioData.Width = stringToFloat(ResolutionArray[0]);
	AspectRatioData.Height = stringToFloat(ResolutionArray[1]); // to int not float?
	AspectRatioData.AspectRatio = roundDecimalPlaces(AspectRatioData.Width / AspectRatioData.Height, 3);
	return AspectRatioData;
}

GetModifiedRotationAngle(targetAvatar, rightStickDiffY, ratio)
{
	
	if((abs(rightStickDiffY - targetAvatar.rotation_total) > 100))
	{
		if(rightStickDiffY >= 270)
		{
			targetAvatar.addtobaseangle = targetAvatar.addtobaseangle + (-360 * ratio);
			if(targetAvatar.addtobaseangle == -360)
			{
				targetAvatar.addtobaseangle = 0;
			}
		}
		if(rightStickDiffY <= 100)
		{
			targetAvatar.addtobaseangle = targetAvatar.addtobaseangle + (360 * ratio);
			if(targetAvatar.addtobaseangle == 360)
			{
				targetAvatar.addtobaseangle = 0;
			}
		}
	}
	  
	targetAvatar.rotation_total = rightStickDiffY;
	
	AddtoAngle = ((rightStickDiffY * ratio) + targetAvatar.addtobaseangle);
	
	return AddtoAngle;
}
cao_update( camera, targetAvatar, camParams )
{
	/*
	cao_params = 
	[
			// [ tag, dist, camZoff ],
			[ "j_spineupper",	100,	0 ], // gender
			[ "j_spineupper",	50,		4 ], // shirt
			[ "J_head",			30,		0 ], // head
			[ "j_knee_ri",		45,		8 ], // pants
			[ "j_head",			25,		0 ], // eyewear
			[ "j_head",			25,		4 ], // hat
			[ "j_spineupper",	50,		-4 ], // gear
			[ "j_knee_ri",		30,		0 ], // kneepads
			[ "j_wrist_le",		40,		4 ], // gloves
			[ "j_ankle_ri",		40,		12 ], // shoes
			[ "j_spineupper",	100,	0 ] // exo
	];
	
	params = cao_params[ level.prv_vl_cao_focus ];
	tag = params[0];
	dist = params[1];
	camzoff = params[2];
	target = targetAvatar GetTagOrigin( tag );
	targetzoff = target[2] - targetAvatar.origin[2];
	cameraZOff = camzoff;
	
	/* // if we want a custom animation for selected part
	if(isdefined(tag) && tag == "j_wrist_le")
	{
		if(!IsDefined(targetAvatar.cao_anim))
		{
			targetAvatar.cao_anim = false;
		}
		if(targetAvatar.cao_anim == false)
		{
			targetAvatar SetAnimState( "lobby_idle", 4 );
			targetAvatar.cao_anim = true;
		}
	}
	*/
	
	self RightStickRotateAvatar(targetAvatar, 0.5);
	
	/*
	dRotX = hacky_get_delta_xrot( camParams, rightStick[0] );
	camParams.zoom += dRotX/360;
	if (camParams.zoom < 0)
		camParams.zoom = 0;
	else if (camParams.zoom > 1)
		camParams.zoom = 1;
		*/
	camParams.CAOtoLobbyFrameTimer = camParams.CAOtoLobbyFrameDelay; // set the delay to wait before camera snaps to lobby after CAO menu fade
	start_origin = targetAvatar.origin + (0, 0, -20) + (AnglesToForward(targetAvatar.spawn_angles) * 120);
		
		
	camParams.zoom = camParams.CAO_CAMERA_RATIO;
	
	// from prelobby
	camParams.dist = camParams.CAO_NEARDIST + (camParams.CAO_FARDIST - camParams.CAO_NEARDIST)*camParams.zoom;
	cameraZOff = camParams.CAO_CAMERAZOFF + camParams.CAO_CAMERAZOFF_ZOOM*camParams.zoom;
	targetzoff = camParams.CAO_TARGETZOFF + camParams.CAO_TARGETZOFF_ZOOM*(1 - camParams.zoom);
	camParams.tgt_camoffset_ratio = camParams.CAO_CAMOFFSET_RATIO + camParams.PRELOBBY_RATIO_ZOOM*(camParams.zoom - 1);
	// level.camParams.camoffset_ratio_maxspeed
	camera update_camera_params_ratio( camParams );
	ch_cyl_zoff = camParams.CH_CYL_ZOFF_NEAR + ( camParams.CH_CYL_ZOFF_FAR - camParams.CH_CYL_ZOFF_NEAR )*camParams.zoom;
	
	// we will transition to the required position
	target_origin = targetAvatar.origin + (0,0,targetzoff);
//	c2t = target_origin - camera.startorigin;	// always use startorigin as our reference, in case we've come back from orbit mode
	c2t = target_origin - (start_origin + camParams.origin_offset);	// always use startorigin as our reference, in case we've come back from orbit mode
	c2t = camParams.dist*VectorNormalize( c2t );
	c2t = (c2t[0], c2t[1], -1*cameraZOff );
	c2t = camParams.dist*VectorNormalize( c2t );
	newCamOrigin = target_origin - c2t;
	
	if ( IsDefined( level.caoAvatarPosOffset ) )
	{
		target_origin = target_origin + level.caoAvatarPosOffset;
		newCamOrigin = newCamOrigin + level.caoAvatarPosOffset;
	}
	
	// keep camParams.angle up to date so we'll lerp from it if we transition
	t2c = -1*c2t;
	t2cangles = VectorToAngles( t2c );
	camParams.angle = t2cangles[1] - targetAvatar.startangles[1];	// since we will reset to these when we transition
	
	movedist = Distance( camera.origin, newCamOrigin );
	movetime = movedist / camParams.PRELOBBY_MOVESPEED;
	if (movetime < 0.1)
		movetime = 0.1;
	camera CamMove( newCamOrigin, movetime, movetime*0.5, movetime*0.5 );
	
	// we want the camera pointing at the avatar
	v = calc_target_dir( newCamOrigin, target_origin, camParams );
	angles = VectorToAngles( v );
	camera CamRotate( angles, movetime, movetime*0.5, movetime*0.5 );

	if ( isdefined(camera.cut) )
	{
		camera DontInterpolate();
		camera.cut = undefined;
	}
}

start_menu_update( camera, camParams )
{
}

clan_profile_update( camera, targetAvatar, camParams )
{	
	// Once designed, set up the camera & movemement controls for switching
	// between clan highlights
}

monitor_vl_mode_change()
{
	self endon("disconnect");
	
	while (true)
	{
		mode = GetDvarInt( "virtualLobbyMode", 0 );
		if ( level.camParams.mode != "cao" )
		{
			if ( ( level.camParams.mode != "prelobby" ) && ( mode == LUI_MODE_PRELOBBY ) )
			{
				level.camParams.newmode = "game_lobby"; // go to game_lobby instead of prelobby
			}
			if ( ( string_find( level.camParams.mode, "prelobby" ) >= 0 ) && ( mode == LUI_MODE_LOBBY ) )
			{
				level.camParams.newmode = "game_lobby"; // go to game_lobby instead of prelobby
			}
		}
		wait 0.05;
	}
}




FindPositionNum()
{
	position_number = self.script_noteworthy;
	position_number = int(position_number);
	return position_number;
}

monitor_player_removed()
{  
	self waittill("disconnect");

	self.camera Delete();
	vlprint( "remove all ownerIds due to disconnect\n");
	foreach ( xuid, ownerId in level.xuid2ownerId )
	{
		remove_avatar( ownerId );
	}
}

// set agent values like agent mesh, health, owner, and team
set_agent_values( team, sessionteam )
{
	// set the agent to the player's team, need a wayway to modify agent name because now it is exactly the same as the owner and it is confusing
	self set_agent_team( team );
	
	self.agent_gameParticipant = false;
	self.isActive 	= true;
	self.spawnTime 	= GetTime();
	self.IsSniper = false; // based on owner's class
	self.sessionteam = sessionteam;;
}

// copied from _bots_strategy.gsc so we don't need to include it
bot_disable_tactical_goals()
{
	self.disable_tactical_goals = true;
	self.tactical_goals = [];
}

wait_load_costume_show( agent )
{
	if ( IsAlive( agent ) )
	{
		isLevelCAC = isdefined( level.cac );
		isPracticeNonOwner =  GetDvarInt( "practiceroundgame" ) && agent.ownerid != 0;
	
		if ( !isLevelCAC && !isPracticeNonOwner )
		{
			show_avatar( agent );
		}
	}
}

wait_load_costume_timeout( seconds, agent )
{
	self endon( "disconnect" );
	agent endon( "wait_load_costume" );
//	self endon( "wait_load_costume" );
	agent endon( "death" );

	wait( seconds );
	
	wait_load_costume_show( agent );
	
	agent notify( "wait_load_costume" );
}

wait_load_costume( agent )
{
	self endon( "disconnect" );
	
//	self notify( "wait_load_costume" ); // do we need this noified on player? in what case would notify on agent fail?
//	self endon( "wait_load_costume" );
	
	agent notify( "wait_load_costume" );
	agent endon( "wait_load_costume" );
	agent endon( "death" );
	
	hide_avatar( agent );
	
	self thread wait_load_costume_timeout( 5.0, agent );
	
	allLoaded = self LoadCostumeModels( agent.costume, agent.team );
	while ( !allLoaded )
	{
		wait( 0.1 );
		allLoaded = self LoadCostumeModels( agent.costume, agent.team );
	}
	
	wait_load_costume_show( agent );
	
	agent notify( "wait_load_costume" );
}

#using_animtree( "multiplayer_vlobby" );
spawn_an_avatar( spawnpoint, primaryWeapon, secondaryWeapon, primWeap, equipment, offhand, perks, costume, memberName, ownerid, is_CAO_agent )
{
	team = "spectator";
	sessionTeam = "none";
	agent = alloc_avatar();
	spawnpoint.spawned_avatar = agent;
	agent.avatar_spawnpoint = spawnpoint;
	agent.costume = costume;
	agent.activeCostume = 0;
//	agent.rotation_offset = 15; // for old horse shoe lobby
	agent.rotation_offset = 0;
	agent set_agent_values( team, sessionTeam );
	//agent SetSpawnWeapon( primaryWeapon );
	ground_origin = GetGroundPosition( agent.avatar_spawnpoint.origin, 20, 512, 120 );
	agent.spawn_angles = (spawnpoint.angles[0], spawnpoint.angles[1] + agent.rotation_offset, spawnpoint.angles[2]); // offset because camera is offset, want facing camera or at least tune it
	agent.angles = agent.spawn_angles;
	agent Show();

	agent.ownerid = ownerid;
	//agent.agentname = memberName;	// TODO: agentname is now a localized string, so we can't use this field to store the player name
	agent SetPlayerAngles(agent.spawn_angles);
	agent SetOrigin( ground_origin, true );
	agent.angles = agent.spawn_angles;
	agent.startangles = agent.spawn_angles;
	agent.storedangleY = agent.angles[1];
	agent.mouseRot = 0;
	agent.storedRightStickY = 0;
	agent.rotation_total = 0;
	agent.addtobaseangle = 0;
	agent.rotation_parent = spawn("script_origin", agent.origin);
//	agent LinkToSynchronizedParent(agent.rotation_parent);

	
//	agent.framedelay = 10;
	agent.camera_cut = true;

	agent.camera_tag_origin = (0,0,0);
	agent.camera_tag_angles = (0,0,0);
		
	agent MotionBlurHQEnable();
	if(!isdefined(self.spawned_avatar))
	{
		self.spawned_avatar = agent;
	}
	
	// if the the player has just joined the lobby keep the camera stable so it doesn't spin. 
	
	agent EnableAnimState( true );
	
	if(!isdefined(is_CAO_agent))
	{
		is_CAO_agent = false;
	}
	
	if(is_CAO_agent == false)
	{
		level.vlavatars[ownerid] = agent;
		animdata = agent GetAnimData(primWeap);
		agent use_animstate( animdata.alias, true, animdata.animstate );
		agent thread CheckWeapChange(primaryWeapon); // we want check for attachments so we need full name
	}
	else
	{
		agent.is_CAO_agent = true;
		primaryWeapon = undefined;
		agent.primaryWeapon = undefined;
		primWeap = undefined;
		
	//	animdata = agent GetAnimData(primWeap);
	//	agent use_animstate( animdata.alias, true, animdata.animstate );
	//	agent thread CheckWeapChange(primWeap);
	//	agent SetAnimClass( "vlobby_animclass" );
		agent use_animstate( "cao_01", true, "lobby_idle" );
		
	}
	
	agent maps\mp\gametypes\_spawnlogic::addToCharactersArray();
	
	if(IsALive(agent))
	{
		vl_avatar_loadout( undefined, ownerid, primaryWeapon, secondaryWeapon, primWeap, equipment, offhand, perks, costume, agent );

		if ( level.players.size > 0 )
		{
			level.players[0] thread wait_load_costume( agent );
		}
	}
	
	if (isdefined( level.cac ))
	{
		// hide_avatar( agent );	// hide any new agents while we are in cac
	}
	// Hide all non-owner avatars in practice round
	if ( GetDvarInt( "practiceroundgame" ) && ownerid != 0 )
		hide_avatar( agent );
	else
		show_avatar( agent );
	
	return agent;
}


CheckWeapChange(primaryWeapon)
{
	self endon( "disconnect" );		// self is the agent here, this disconnect should probably actually be on the player
	self endon( "death" );
	self endon( "free_avatar" );

	if( !IsDefined( self.primaryWeapon ) )
	{
		self.primaryWeapon = primaryWeapon;
	}
	self.stored_weapon = self.primaryWeapon;

	WeaponString = undefined;
	
	while(true)
	{
		if ( !IsDefined( self.primaryWeaponEnt ) || !IsDefined( self.akimboWeaponEnt ) )
		{
			wait 0.05;
			continue;
		}
		    
		if(IsDefined(self) && IsDefined(self.primaryWeapon) && IsALive(self) )
		{
			WeapStringArray = StrTok(self.primaryWeapon, "_");
			WeaponString = WeapStringArray[0] + "_" + WeapStringArray[1];
		}
		
		// 	From Evan
		CurrentAnim = self GetAnimEntry();
		CaoAnim = self GetAnimEntry( "lobby_idle", "cao_01" );
		animdata = self GetAnimData(WeaponString);
		DesiredAnim = self GetAnimEntry(animdata.animstate, animdata.alias);
				
		if(isdefined(level.camParams.mode))
		{
			/#
			/// so animators can test animations easier in the lobby
			if ( GetDvarInt( "scr_vl_anim_choose_anim_enable", 0 ) )
			{
				if(self == level.vlavatars[level.vl_focus])
				{
					///// debug for selecting own animation;
					animdata = self GetAnimData(WeaponString);
					
					
					anim_index = GetDvarInt( "scr_vl_anim_index", 0 );
					AnimCount = self GetAnimEntryCount(animdata.animstate);
					
					if(anim_index >= 0 && anim_index <= (AnimCount - 1))
					{
						DesiredAnim = self GetAnimEntry(animdata.animstate, anim_index);
					// alias = self GetAnimEntryAlias(animdata.animstate, anim_index);
					
						if(CurrentAnim != DesiredAnim)
						{
							// level.camParams.framedelay = 6;
							self.stored_weapon = self.primaryweapon; // store the full weapon name
							
							self use_animstate( anim_index, undefined, animdata.animstate );
						}
					}
				}
				// so we don't run the normal code below
				wait(0.05);
				continue;
			}
			#/
		
			if(level.camParams.newmode != "cao" && level.camParams.mode != "cao")
			{
				if(IsDefined(self) && IsDefined(WeaponString) && IsALive(self) )
				{
					weaponLoaded = IsWeaponLoaded( self.primaryWeapon );
								
					changedWeapon = self.stored_weapon != self.primaryweapon;
					needUpdate = changedWeapon || (CurrentAnim != DesiredAnim);
					DiffBaseWeapon = GetWeaponBaseName(self.stored_weapon) != GetWeaponBaseName(self.primaryweapon); // find out if it is same base weapon just with diff attachments
					
					if( needUpdate )
					{		
						if ( weaponLoaded )
						{
							if(DiffBaseWeapon) // checking to see if it is a diff base weapon, if not we don't want to hide, just update attachments
							{
								hide_avatar_primary_weapon( self ); // hide weapons once the new one has loaded so it doesn't look crazy in transition
								hide_avatar_akimbo_weapon( self );
								thread AttachWeaponDelayed(self); // if we attach same frame as we hide you see weapon like shield turn 90 degrees in 1 frame
								thread ShowPrimaryDelayed(self);  // show the weapon shortly after transition starts
							}	
							self.primaryWeaponEnt SetPickupWeapon( self.primaryWeapon );
							self.akimboWeaponEnt SetPickupWeapon( self.primaryWeapon ); // might as well set the akimbo mesh here even if we don't show it just incase we do need to show it it should be the correct mesh
							
							if( IsSubStr( self.primaryWeapon, "akimbo" ) || IsSubStr( self.primaryWeapon, "akimboxmg"))
							{
								if(DiffBaseWeapon)
								{
									thread ShowAkimboDelayed(self);
								}
							}
							else
							{
								self.akimboWeaponEnt hide();
								self.akimboWeaponEnt HideAllParts();
							}
							self.stored_weapon = self.primaryweapon; // when its actually loaded thats when we store this now
							animdata = self GetAnimData(WeaponString);
							self.camera_state = animdata.camera_state;
						//	self use_animstate(animdata.alias, undefined, animdata.animstate );
							self thread AnimDelayed(animdata.alias, animdata.animstate ); // delay anim so that that hiding and AttachWeaponDelayed() can run
						}
						else
						{
							// From Evan - I don't think we need this.  If GetAnimData() fails to find the anim it returns CAO anim in the data.  
							// Sometimes when the weapon is not loaded it plays CAO anim for a moment and then swaps to the correct anim,
							/// it looks janky and weird. 
						//	self use_animstate( "cao_01", undefined, "lobby_idle" );
						}
					}
				}
			}
		}
		wait(0.5);
	}
}

// has this weapon been loaded? bool
IsWeaponLoaded(weapon)
{
	weaponLoaded = false;
	
	if ( level.players.size > 0 )
	{
		weaponLoaded = level.players[0] WorldWeaponsLoaded( weapon );
		if ( !weaponLoaded )
		{
			level.players[0] LoadWeapons( weapon );
		}
	}
	
	return weaponLoaded;
}

// delay attaching weapon to bone
AttachWeaponDelayed(agent)
{
	wait(level.camParams.CACWeaponAttachDelayTime);
	AttachPrimaryWeapon(agent);
}

// so that weapons have time to hide before anim kicks off and makes them look weird. (knife / shield) 
AnimDelayed(alias, animstate )
{
	wait(level.camParams.CACWeaponAnimDelayTime);
	self use_animstate(alias, undefined, animstate );
}

#using_animtree( "multiplayer_vlobby" );
GetAnimData(primWeap)
{
	animdata = SpawnStruct();
	animdata.alias = primWeap;
	
	if(IsDefined(primWeap) && primWeap != "" && primWeap != "none")
	{
		animdata.animstate = TableLookup( "mp/vlobby_cac_offsets.csv", 0, primWeap, 5 );
		animdata.camera_state = TableLookup( "mp/vlobby_cac_offsets.csv", 0, primWeap, 6 );
	}
	
	if(!IsDefined(animdata.animstate) || animdata.animstate == "")
	{
		animdata.animstate = "lobby_idle";
		animdata.alias = "cao_01";
		animdata.camera_state = "stand";
	}
	self SetAnimClass( "vlobby_animclass" );
	
	if(!IsDefined(animdata.camera_state))
	{
		animdata.camera_state = "stand";
	}
	if(!IsDefined(self.camera_state))
	{
		self.camera_state = animdata.camera_state;
	}

	return animdata;
}

vl_give_weapons( player, agent )
{
	//Set misc models for arm launcher and/or abilities.
	AddLaunchers(agent);
	
	if (!isdefined( agent.primaryWeapon ) )
		return;	// weapons haven't been setup yet, so don't try to give them
	
	if (!isdefined(agent.animAlias))
	{
		assertMsg( "VL Agent doesn't have an animstate" );
	}
	else
	{	// get the models and link them
		if (agent.primaryWeapon != "none")
		{
			if (!isdefined(agent.primaryWeaponEnt))
			{
				primaryWeaponEnt = spawn( "weapon_" + agent.primaryWeapon, (0,0,0) );
				xuid = get_xuid_for_avatar( agent );
				setEntPlayerXuidForEmblem( primaryWeaponEnt, xuid );

				agent.primaryWeaponEnt = primaryWeaponEnt;
				primaryWeaponEnt.primaryweapon = agent.primaryWeapon;
				agent.primaryWeaponEnt show(); // when it first spawns in show it.
				agent.primaryWeaponEnt ShowAllParts();
				agent.primaryWeaponEnt SetPickupWeapon( agent.primaryWeapon );
				
				AttachPrimaryWeapon(agent);
			}
			
			else if (agent.primaryWeaponEnt.primaryWeapon != agent.primaryWeapon)
			{
			//	agent.primaryWeaponEnt SetPickupWeapon( agent.primaryWeapon ); // handle in CheckWeapChange(primaryWeapon)
				agent.primaryWeaponEnt.primaryweapon = agent.primaryWeapon;
			}		
// don't atutomatically show attach changed weapons here.  Handle it in CheckWeapChange(primaryWeapon)
			
			
			/////////////////////////////////////// Akimbo weapons  /////////////////////////////////
			//////
			if (!isdefined(agent.akimboWeaponEnt))
			{
				akimboWeaponEnt = spawn( "weapon_" + agent.primaryWeapon, (0,0,0) );
				xuid = get_xuid_for_avatar( agent );
				setEntPlayerXuidForEmblem( akimboWeaponEnt, xuid );
				agent.akimboWeaponEnt = akimboWeaponEnt;
				akimboWeaponEnt.primaryweapon = agent.primaryWeapon;
			}
			else if (agent.akimboWeaponEnt.primaryWeapon != agent.primaryWeapon)
			{
			//	agent.akimboWeaponEnt SetPickupWeapon( agent.primaryWeapon ); // handle in CheckWeapChange(primaryWeapon)
				agent.akimboWeaponEnt.primaryweapon = agent.primaryWeapon;
			}
			tag_left = "tag_weapon_left";
			
			if( IsSubStr( agent.akimboWeaponEnt.primaryWeapon, "combatknife" ) )
				tag_left = "tag_inhand";
			
			if ( IsSubStr( agent.akimboWeaponEnt.primaryWeapon, "riotshield" ) )
				tag_left = "tag_weapon_left";
			
			agent.akimboWeaponEnt unlink();
			agent.akimboWeaponEnt.origin = agent GetTagOrigin(tag_left);
			agent.akimboWeaponEnt.angles = agent GetTagAngles(tag_left);
			agent.akimboWeaponEnt LinkToSynchronizedParent( agent, tag_left );
			agent.akimboWeaponEnt SetOwnerOriginal( player );			
			
			if( IsSubStr( agent.primaryWeaponEnt.primaryWeapon, "akimbo" ) || IsSubStr( agent.primaryWeaponEnt.primaryWeapon, "akimboxmg" ))
			{
				thread ShowAkimboDelayed(agent);
			}
			else if(!isdefined(agent.stored_weapon)) // if avatar is new and not akimbo weapon then hide them
			{
				agent.akimboWeaponEnt hide();
				agent.akimboWeaponEnt HideAllParts();
			}
			
			
			//////
			////////////////////////////////////////////////////////////////////
			
			
		}
		if ( agent.secondaryWeapon != "none" )
		{
			if ( IsSubStr( agent.secondaryWeapon, "combatknife" ) )
			{
				if ( IsDefined( agent.secondaryWeaponEnt ) )
				{
					if ( agent.secondaryWeaponEnt IsLinked() )
					{
						agent.secondaryWeaponEnt Unlink();
					}
					agent.secondaryWeaponEnt Delete();
				}
			}
			else
			{
				if (!isdefined(agent.secondaryWeaponEnt))
				{
					secondaryWeaponEnt = spawn( "weapon_" + agent.secondaryWeapon, (0,0,0) );
					xuid = get_xuid_for_avatar( agent );
					setEntPlayerXuidForEmblem( secondaryWeaponEnt, xuid );
					agent.secondaryWeaponEnt = secondaryWeaponEnt;
					secondaryWeaponEnt.secondaryweapon = agent.secondaryWeapon;
				}
				else if (agent.secondaryWeaponEnt.secondaryWeapon != agent.secondaryWeapon)
				{
					agent.secondaryWeaponEnt SetPickupWeapon( agent.secondaryWeapon );
					agent.secondaryWeaponEnt.secondaryweapon = agent.secondaryWeapon;
				}
				// TODO: support stowedOffsetModel from weapondef
				tag = "tag_stowed_back";
				agent.secondaryWeaponEnt unlink();
				agent.secondaryWeaponEnt.origin = agent GetTagOrigin(tag);
				agent.secondaryWeaponEnt.angles = agent GetTagAngles(tag);
				agent.secondaryWeaponEnt LinkToSynchronizedParent( agent, tag );
				agent.secondaryWeaponEnt ShowAllParts();
				agent.secondaryWeaponEnt SetOwnerOriginal( player );
			}
		}
	}
	
}

// AttachPrimaryWeapon.  Moved this block of code so it can be used anywhere.
AttachPrimaryWeapon(agent)
{
	player = undefined;
	if(isdefined(agent.player))
	{
		player = agent.player;
	}
	tag = "tag_weapon_right";
	
	if( IsSubStr( agent.primaryWeaponEnt.primaryWeapon, "combatknife" ) )
		tag = "tag_inhand";
	
	if ( IsSubStr( agent.primaryWeaponEnt.primaryWeapon, "riotshield" ) )
		tag = "tag_weapon_left";
	
	agent.primaryWeaponEnt unlink();
	agent.primaryWeaponEnt.origin = agent GetTagOrigin(tag);
	agent.primaryWeaponEnt.angles = agent GetTagAngles(tag);
	// TODO: support tag_weapon_left decision
	agent.primaryWeaponEnt LinkToSynchronizedParent( agent, tag );
	agent.primaryWeaponEnt SetOwnerOriginal( player );	
}

AddLaunchers(agent)
{
	//Set misc models for arm launcher and/or abilities.
	
	agent Detach("npc_exo_arm_launcher_R", "J_Elbow_RI", false);
	agent Detach("npc_exo_arm_launcher_L", "J_Elbow_LE", false);
	
	//handling wildcards
	if( isdefined( agent.lethal ) && agent.lethal != "specialty_null" )
	{
		//check for dual tacticals
		if( !agent _hasPerk( "specialty_wildcard_dualtacticals" ) )
		{
			//if no dual tacticals attach the launcher
			// are these creating a new ent everytime this function is called?  Should we be spawning a script_model once instead and just hide/show?
			agent attach( "npc_exo_arm_launcher_R", "J_Elbow_RI", true );
		}
		else
		{
			//show ability here.
		}
	}
	
	if( isdefined( agent.tactical ) && agent.tactical != "specialty_null" )
	{
		//check for dual lethals
		if ( agent _hasPerk( "specialty_wildcard_duallethals" )  )
		{
			//if dual lethals attach the left arm launch
			agent attach( "npc_exo_arm_launcher_L", "J_Elbow_LE", true );
		}
		else
		{
			//show ability here.
		}
	}
}

ShowAkimboDelayed(agent)
{	
	agent endon("death");
	agent.primaryWeaponEnt endon("death");
	agent endon( "hide_akimbo_weapon" );
	
	wait(level.camParams.CACWeaponDelayTime); 
	
	if(level.camParams.newmode != "cao" && level.camParams.mode != "cao")
	{
		if( IsSubStr( agent.primaryWeaponEnt.primaryWeapon, "akimbo" ) || IsSubStr( agent.primaryWeaponEnt.primaryWeapon, "akimboxmg"))
		{
			agent.akimboWeaponEnt show();
			agent.akimboWeaponEnt ShowAllParts();	
		}
	}
}


ShowPrimaryDelayed(agent)
{	
	agent endon("death");
	agent.primaryWeaponEnt endon("death");
	agent endon( "hide_primary_weapon" );
	
	wait(level.camParams.CACWeaponDelayTime);
	
	isPracticeNonOwner = GetDvarInt( "practiceroundgame" ) && agent.ownerid != 0;

	if(level.camParams.newmode != "cao" && level.camParams.mode != "cao" && !isPracticeNonOwner)
	{
		agent.primaryWeaponEnt show();
		agent.primaryWeaponEnt ShowAllParts();
	}
	
}

vl_avatar_costume( agent, costume, setcostume )
{
	agent TakeAllWeapons();
	agent DetachAll();
	agent.headModel = undefined;
	if ( isDefined( costume ) )
		agent.costume = costume;
	if ( !IsDefined( agent.costume ) || !maps\mp\gametypes\_teams::validCostume( agent.costume ) )
	{
		if ( isdefined( agent.sessionCostume ) && maps\mp\gametypes\_teams::validCostume( agent.sessionCostume ) )
			agent.costume = agent.sessionCostume;
		else
		{
			agent.costume = maps\mp\gametypes\_teams::getDefaultCostume();
			agent.sessionCostume = agent.costume;
		}
	}
	if (IsDefined(setcostume) && setcostume )
	{
		level.players[0].costume = agent.costume;
		costumeFound = level.players[0] maps\mp\gametypes\_teams::playerCostume();
		assert( costumeFound );
		costumeFound = agent maps\mp\gametypes\_teams::playerCostume();
		assert( costumeFound );
		
		// ensure CaO agent is in synch for whenever we go back to CaO
		caoAgent = level.players[0].cao_agent;
		vl_avatar_costume( caoAgent, agent.costume );
		costumeFound = caoAgent maps\mp\gametypes\_teams::playerCostume();
		assert( costumeFound );		
	}
}

vl_avatar_loadout( player, ownerid, primaryWeapon, secondaryWeapon, primWeap, equipment, offhand, perks, costume, agent )
{
	if(!isdefined(agent))
	{
		agent = level.vlavatars[ownerid];
	}
	canLocalUpdate = false;

	if ( IsDefined( agent.loadout ) && ( agent.loadout.player_controller >= 0 ) )
		canLocalUpdate = true;
	
	vl_avatar_costume( agent, costume );
	/#
	if ( getdvarint("scr_vl_printcostume",0) )
	{
		print( "ownerid " + ownerid + "\n" );
		for ( i=0; i<level.costumeCategories.size; i++ )
		{
			print( "  " + level.costumeCategories[i] + ": " + agent.costume[i] + "\n" );
		}
	}
	#/
	
	agent maps\mp\gametypes\_teams::playerModelForWeapon( primWeap, getBaseWeaponName( secondaryWeapon ) );

	if ( canLocalUpdate && IsDefined( level.players[0] ) )
	{
		level.players[0] SetCostumeModels( agent.costume );
	}

	agent.primaryWeapon = primaryWeapon;
	// agent.secondaryWeapon = secondaryWeapon;
	agent.secondaryWeapon = "none"; // secondary weapons clip into the character models so disabling.  
	agent.tactical = offhand;
	agent.lethal = equipment;
	agent.perks = perks;
	
	vl_give_weapons( player, agent );	
}

monitor_class_select_or_weapon_change( ownerid )
{
	self endon("disconnect");
	
	while (true)
	{
		self waittill( "luinotifyserver", channel, index );
		if ( (channel == "classpreview" || channel == "classpreview_postcopy") && IsDefined(index) )
		{
			if ( index >= 0 )
			{
				controller = index & 15;
				classIndex = int(index / 16);
				vlprint( "classpreview " +  channel + " controller=" + controller + "  class=" + classIndex );
				
				if ( classIndex > 0)
				{
					level.cac = true;	// ensure the cac view (weapon view) is active.
					self.currentSelectedClass = classIndex-1;
				}
				ownerid = GetFocusFromController( controller );
				// If this loadout selection change resulted from a 'copy class/loadout' event, 
				// delay the avatar refresh until the next update cycle, otherwise the avatar 
				// displayed will be showing the destination slot's loadout before copying.
				delayedRefresh = ( channel == "classpreview_postcopy" );
				avatar = level.vlavatars[ownerid];
				if (IsDefined( avatar ) )
				{
					level.vl_focus = ownerid;
					level.vl_local_focus = ownerid;
					virtual_lobby_set_class( ownerid, "lobby" + (self.currentSelectedClass+1), !delayedRefresh );
					level.cac_weapon = avatar.primaryWeapon;
				}
				level.forceAvatarRefresh = delayedRefresh;
			}
			else
			{
				// exiting CaC
				level.cac = undefined;
			}
		}
	}
}

monitor_cac_set_weapon( ownerid, newWeaponName )
{
	ignore = "";
	/#			
	if ( newWeaponName != "none" )
	ignore = tablelookup( "mp/statstable.csv", 4, newWeaponName, 51 );	// for dev
	#/
	if ( ( newWeaponName != "current" ) && ( newWeaponName != "none" ) && ( ignore == "" ) )
	{
		requiredAttachment = tablelookup( "mp/statstable.csv", 4, newWeaponName, 40 );
	
		if ( requiredAttachment == "" )
			requiredAttachment = "none";
		
		weaponName = maps\mp\gametypes\_class::buildWeaponName( newWeaponName,requiredAttachment, "none", "none" );	// w/o attachments here (only required attachments)
	}
	else if ( newWeaponName == "none" )
	{
		weaponName = "none";
	}
	else
	{
		avatar = level.vlavatars[ownerid];
		weaponName = avatar.primaryWeapon;
	}
	level.cac_weapon = weaponName;
}

has_suffix( search_string, suffix )
{
	if ( search_string.size >= suffix.size )
	{
		if ( GetSubStr( search_string, search_string.size - suffix.size, search_string.size ) == suffix )
		{
			return true;
		}
	}
	
	return false;
}

monitor_create_a_class( ownerid )
{
	self endon("disconnect");
	notifies = [];	// we'll ensure we keep any notifies we got while we wait for level.camParams to become valid
	while (!isdefined(level.camParams))
	{
		self waittill( "luinotifyserver", channel, id );
		if ( isDefined(id) )
			notifies[notifies.size] = [ channel, id ];
		else
			notifies[notifies.size] = [ channel ];
	}
	
	while (true)
	{
		if ( notifies.size > 0 )
		{
			note = notifies[0];
			newnotifies = [];
			for (i=1; i<notifies.size; i++ )
				newnotifies[newnotifies.size] = notifies[i];
			notifies = newnotifies;
			channel = note[0];
			if ( note.size > 1 )
				id = note[1];
			else
				id = undefined;
		}
		else
			self waittill( "luinotifyserver", channel, id );
		if ( channel == "cac" && IsDefined(id) )
		{
			if (id == 0)
			{	// closing cac menu
				level.cac = undefined;
			}
			else
			{	// opening cac menu
				level.cac = true;
			}
			level.vl_cao_focus = undefined;
		}
		else if ( (channel == "weapon_highlighted") && IsDefined(id) )
		{
			if ( IsSubStr( id, "stream:" ) )
			{
				tokens = StrTok( id, ":" );
				
				if ( tokens.size > 1 )
				{
					weaponName = tokens[tokens.size - 1];
					if ( !has_suffix( weaponName, "_mp" ) )
					{
						weaponName += "_mp";
					}
					
					vlprintln( "weapon_stream: " + weaponName );
					weaponsArray = [weaponName]; // now weapon is stored in an array and seems to work better
					self LoadWeapons( weaponsArray );
				}
				
				monitor_cac_set_weapon( ownerid, "none" );
			}
			else
			{
				vlprintln( "weapon_highlighted  "+ id );
				monitor_cac_set_weapon( ownerid, id );
			}
		}
		else if ( (channel == "lootscreen_weapon_highlighted") && IsDefined(id) )
		{
			// 'lootscreen_weapon_highlighted' basically combines 'cac' with 'weapon_highlighted' since 
			// we need to send them together frequently on the loot screen
			if ( id == "none" )
			{
				level.cac = undefined;
			}
			else if ( id == "reset" )
			{
				// trying to get offset working in CAC weapon select screen and loot screen but not CAC screen
				// when you hit A button on loot screen this happens.
				level.cac = true;
			}
			else
			{
				// when loot screen starts
				//monitor_cac_set_weapon( ownerid, id ); 
				level.cac = true;
			}
				
			// keep cached camera mode to 'prelobby_loot' when browsing on the loot screen, 
			// we don't want to store 'cac'/'cao' cam modes since we're temporarily switching 
			// among them
			//level.camParams.pushmode = "prelobby_loot";
			// make sure cao mode is off, could be on when moving focus from gear loot to weapon loot

			// Keep this level.vl_cao_focus = undefined;
			level.vl_cao_focus = undefined;
		}
		else
		{
			previewAttach = 0;
			if ( channel == "preview_attach1" )
				previewAttach = 1;
			else if ( channel == "preview_attach2" )
				previewAttach = 2;
			else if ( channel == "preview_attach3" )
				previewAttach = 3;
			
			if ( (previewAttach > 0) && IsDefined(id) )
			{
				avatar = level.vlavatars[ownerid];
				entry = avatar.loadout;
				
				primary = TableLookup( "mp/statstable.csv", 0, entry.primary, 4 );
				primaryAttachment1 = TableLookup( "mp/attachmenttable.csv", 0, entry.primaryattachment1, 3 );
				primaryAttachment2 = TableLookup( "mp/attachmenttable.csv", 0, entry.primaryattachment2, 3 );
				primaryAttachment3 = TableLookup( "mp/attachmenttable.csv", 0, entry.primaryattachment3, 3 );
				primaryCamo = entry.primaryCamo;
				
				if ( id != "current" )
				{
					if ( previewAttach == 1)
					{
						primaryAttachment1 = id;
					}
					else if ( previewAttach == 2 )
					{
						primaryAttachment2 = id;
					}
					else if ( previewAttach == 3 )
					{
						primaryAttachment3 = id;
					}
				}
	
				level.cac_weapon = maps\mp\gametypes\_class::BuildWeaponName( primary, primaryAttachment1, primaryAttachment2, primaryAttachment3, primaryCamo, undefined/* primaryReticle*/ );
			}		
		}
	}
}

GetFocusFromController( controller )
{
	foreach ( idx, avatar in level.vlavatars )
	{
		if ( IsDefined( avatar.loadout ) && IsDefined( avatar.loadout.player_controller ) && ( avatar.loadout.player_controller == controller ) )
			return idx;
	}
	vlprintln( "unable to find avatar for controller " + controller );
	return -1;	// default to 0 when we can't find the focus.
}

virtual_lobby_set_class( ownerid, class, refreshAvatar, force, agent )
{
	if( (isDefined( self.pers["class"] ) && self.pers["class"] == class) && ( !IsDefined(force) || !force ) )
		return;

	self.pers["class"] = class;
	self.class = class;

	self maps\mp\gametypes\_class::setClass( self.pers["class"] );
	self.tag_stowed_back = undefined;
	self.tag_stowed_hip = undefined;
	self maps\mp\gametypes\_class::giveAndApplyLoadout( self.pers["team"], self.pers["class"] );
	
	if ( refreshAvatar )
	{
		vl_avatar_loadout( self, ownerid, self.primaryWeapon, self.secondaryWeapon, self.pers["primaryWeapon"], self.loadoutEquipment, self.loadoutOffhand, self.perks, self.costume );
		if(isdefined(self.cao_agent))
		{
			vl_avatar_loadout( self, ownerid, undefined, self.secondaryWeapon, self.pers["primaryWeapon"], self.loadoutEquipment, self.loadoutOffhand, self.perks, self.costume, self.cao_agent );
		}
	}
}


grab_players_classes()
{
	customclasses = [ "privateMatchCustomClasses", "customClasses" ];
	self.loadouts = [];
	self.currentSelectedClass = 0;
	
	foreach (class in customclasses)
	{
		level.forceCustomClassLoc = class;
		self.loadouts[class] = [];
		for (class_num=0; class_num<10; class_num++)	// note the number may need to come from playerdata
		{
			loadout = [];
			loadout["primary"] = maps\mp\gametypes\_class::cac_getWeapon( class_num, 0 );
			loadout["primaryAttachment1"] = maps\mp\gametypes\_class::cac_getWeaponAttachment( class_num, 0 );
			loadout["primaryAttachment2"] = maps\mp\gametypes\_class::cac_getWeaponAttachmentTwo( class_num, 0 );
			loadout["primaryAttachment3"] = maps\mp\gametypes\_class::cac_getWeaponAttachmentThree( class_num, 0 );
			loadout["primaryCamo"] = maps\mp\gametypes\_class::cac_getWeaponCamo( class_num, 0 );
			loadout["primaryReticle"] = maps\mp\gametypes\_class::cac_getWeaponReticle( class_num, 0 );
			for (i=0; i<6; i++)
			{
				loadout["perk"+i] = maps\mp\gametypes\_class::cac_getPerk( class_num, i );
			}
			for (i=0; i<3; i++)
			{
				loadout["wildcard"+i] = maps\mp\gametypes\_class::cac_getWildcard( class_num, i );
			}
			loadout["secondary"] = maps\mp\gametypes\_class::cac_getWeapon( class_num, 1 );
			loadout["secondaryAttachment1"] = maps\mp\gametypes\_class::cac_getWeaponAttachment( class_num, 1 );
			loadout["secondaryAttachment2"] = maps\mp\gametypes\_class::cac_getWeaponAttachmentTwo( class_num, 1 );
			loadout["secondaryAttachment3"] = maps\mp\gametypes\_class::cac_getWeaponAttachmentThree( class_num, 1 );
			loadout["secondaryCamo"] = maps\mp\gametypes\_class::cac_getWeaponCamo( class_num, 1 );
			loadout["secondaryReticle"] = maps\mp\gametypes\_class::cac_getWeaponReticle( class_num, 1 );
			loadout["equipment"] = maps\mp\gametypes\_class::cac_getEquipment( class_num, 0 );
			loadout["offhand"] = maps\mp\gametypes\_class::cac_getEquipment( class_num, 1 );
			self.loadouts[class][class_num] = loadout;
		}
	}
	level.forceCustomClassLoc = undefined;
}

update_local_class( primary, primaryAttachment1, primaryAttachment2, primaryAttachment3, primaryCamo, primaryReticle, secondary, secondaryAttachment1, secondaryAttachment2, secondaryCamo, secondaryReticle, lethal, tactical, wildcard1, wildcard2, wildcard3, perk0, perk1, perk2, perk3, perk4, perk5 )
{
	class_num = int(self.currentSelectedClass);
	class = cac_getCustomClassLoc();
	self.loadouts[class][class_num]["primary"] = primary;
	self.loadouts[class][class_num]["primaryAttachment1"] = primaryAttachment1;
	self.loadouts[class][class_num]["primaryAttachment2"] = primaryAttachment2;
	self.loadouts[class][class_num]["primaryAttachment3"] = primaryAttachment3;
	self.loadouts[class][class_num]["primaryCamo"] = primaryCamo;
	self.loadouts[class][class_num]["primaryReticle"] = primaryReticle;
	self.loadouts[class][class_num]["secondary"] = secondary;
	self.loadouts[class][class_num]["secondaryAttachment1"] = secondaryAttachment1;
	self.loadouts[class][class_num]["secondaryAttachment2"] = secondaryAttachment2;
	self.loadouts[class][class_num]["secondaryCamo"] = secondaryCamo;
	self.loadouts[class][class_num]["secondaryReticle"] = secondaryReticle;
	self.loadouts[class][class_num]["equipment"] = lethal;
	self.loadouts[class][class_num]["lethal"] = lethal;
	self.loadouts[class][class_num]["offhand"] = tactical;
	self.loadouts[class][class_num]["tactical"] = tactical;
	self.loadouts[class][class_num]["wildcard0"] = wildcard1;
	self.loadouts[class][class_num]["wildcard1"] = wildcard2;
	self.loadouts[class][class_num]["wildcard2"] = wildcard3;
	self.loadouts[class][class_num]["perk0"] = perk0;
	self.loadouts[class][class_num]["perk1"] = perk1;
	self.loadouts[class][class_num]["perk2"] = perk2;
	self.loadouts[class][class_num]["perk3"] = perk3;
	self.loadouts[class][class_num]["perk4"] = perk4;
	self.loadouts[class][class_num]["perk5"] = perk5;
}

loadout_changed( orgLoadout, newLoadout )
{
	if (!IsDefined(orgLoadout))
		return true;
	if ( orgLoadout.primary != newLoadout.primary )
		return true;
	if ( orgLoadout.primaryattachment1 != newLoadout.primaryattachment1 )
		return true;
	if ( orgLoadout.primaryattachment2 != newLoadout.primaryattachment2 )
		return true;
	if ( orgLoadout.primaryattachment3 != newLoadout.primaryattachment3 )
		return true;
	if ( orgLoadout.primarycamo != newLoadout.primarycamo )
		return true;
	if ( orgLoadout.secondary != newLoadout.secondary )
		return true;
	if ( orgLoadout.secondaryattachment1 != newLoadout.secondaryattachment1 )
		return true;
	if ( orgLoadout.secondaryattachment2 != newLoadout.secondaryattachment2 )
		return true;
	if ( orgLoadout.secondarycamo != newLoadout.secondarycamo )
		return true;
	if ( orgLoadout.tactical != newLoadout.tactical )
		return true;
	if ( orgLoadout.lethal != newLoadout.lethal )
		return true;
	
	return false;
}

costume_changed( orgCostume, newCostume )
{
	if ( !isdefined( orgCostume ) )
	{
		if ( !isdefined( newCostume ) )
			return false;
		return true;
	}
	if ( orgCostume.size != newCostume.size )
		return true;
	if ( !maps\mp\gametypes\_teams::validCostume( newCostume ) )
		return false; // if the communicated costume is invalid, then flag as no change
	for ( i = 0; i < newCostume.size; i++ )
	{
		if ( newCostume[i] < 0 )
			return false;	// if any entries in the new costume are invalid, no change
		if ( orgCostume[i] != newCostume[i] )
			return true;
	}
	return false;
}

setDefaultCostumeIfNeeded( costume )
{
	if ( costume[level.costumeCat2Idx["head"]] == 0 )
		costume[level.costumeCat2Idx["head"]] = 1;
	if ( !costume[level.costumeCat2Idx["shirt"]] )
		costume[level.costumeCat2Idx["shirt"]] = 1;
	if ( !costume[level.costumeCat2Idx["pants"]] )
		costume[level.costumeCat2Idx["pants"]] = 1;
	if ( !costume[level.costumeCat2Idx["gloves"]] )
		costume[level.costumeCat2Idx["gloves"]] = 1;
	if ( !costume[level.costumeCat2Idx["shoes"]] )
		costume[level.costumeCat2Idx["shoes"]] = 1;
	if ( !costume[level.costumeCat2Idx["gear"]] )
		costume[level.costumeCat2Idx["gear"]] = 1;
	if ( !costume[level.costumeCat2Idx["exo"]] )
		costume[level.costumeCat2Idx["exo"]] = 1;
	return costume;
}


// this is actually where other players that are not THE player get their gear etc and then start the avatar/agent spawn process
monitor_member_class_changes()
{
	level.mccqueue = [];
	
	while (true)
	{	// it looks like there are situations where level.players is reset to undefined for some period of time
		// wait until we've finished the initialization.  Ensure we've setup our camera parameters, so the spawn_an_avatar call can properly setup
		while ( !isdefined( level.players ) || ( level.players.size == 0 ) || !IsDefined( level.camParams ) )
		{
			wait 0.05;
		}
		
		while ( isdefined( level.players ) && ( level.players.size > 0 ) )
		{
			// now apply each entry.
			foreach (entry in level.mccqueue)
			{
				primary = TableLookup( "mp/statstable.csv", 0, entry.primary, 4 );
				primaryAttachment1 = TableLookup( "mp/attachmenttable.csv", 0, entry.primaryattachment1, 3 );
				primaryAttachment2 = TableLookup( "mp/attachmenttable.csv", 0, entry.primaryattachment2, 3 );
				primaryAttachment3 = TableLookup( "mp/attachmenttable.csv", 0, entry.primaryattachment3, 3 );
				primaryCamo = entry.primaryCamo;
				primaryCamoName = TableLookup( "mp/camoTable.csv", 0, entry.primaryCamo, 1 );
				primaryReticle = entry.primaryReticle;
				primaryReticleName = TableLookup( "mp/reticleTable.csv", 0, entry.primaryReticle, 1 );
				secondary = TableLookup( "mp/statstable.csv", 0, entry.secondary, 4 );
				secondaryAttachment1 = TableLookup( "mp/attachmenttable.csv", 0, entry.secondaryattachment1, 3 );
				secondaryAttachment2 = TableLookup( "mp/attachmenttable.csv", 0, entry.secondaryattachment2, 3 );
				secondaryAttachment3 = "none";
				secondaryCamo = entry.secondaryCamo;
				secondaryCamoName = TableLookup( "mp/camoTable.csv", 0, entry.secondaryCamo, 1 );
				secondaryReticle = entry.secondaryReticle;
				secondaryReticleName = TableLookup( "mp/reticleTable.csv", 0, entry.secondaryReticle, 1 );
				lethal = TableLookup( "mp/perktable.csv", 0, entry.lethal, 1 );
				tactical = TableLookup( "mp/perktable.csv", 0, entry.tactical, 1 );
				wildcard1 = TableLookup( "mp/perktable.csv", 0, entry.wildcard1, 1 );
				wildcard2 = TableLookup( "mp/perktable.csv", 0, entry.wildcard2, 1 );
				wildcard3 = TableLookup( "mp/perktable.csv", 0, entry.wildcard3, 1 );
				perk1 = TableLookup( "mp/perktable.csv", 0, entry.perk1, 1 );
				perk2 = TableLookup( "mp/perktable.csv", 0, entry.perk2, 1 );
				perk3 = TableLookup( "mp/perktable.csv", 0, entry.perk3, 1 );
				perk4 = TableLookup( "mp/perktable.csv", 0, entry.perk4, 1 );
				perk5 = TableLookup( "mp/perktable.csv", 0, entry.perk5, 1 );
				perk6 = TableLookup( "mp/perktable.csv", 0, entry.perk6, 1 );
				
				primaryWeapon = maps\mp\gametypes\_class::BuildWeaponName( primary, primaryAttachment1, primaryAttachment2, primaryAttachment3, primaryCamo, primaryReticle );
				secondaryWeapon = maps\mp\gametypes\_class::BuildWeaponName( secondary, secondaryAttachment1, secondaryAttachment2, secondaryAttachment3, secondaryCamo, secondaryReticle );
				weaponName = getBaseWeaponName( primaryWeapon );
				
				costume = [];
				costume[level.costumeCat2Idx["gender"]] = entry.gender;
				costume[level.costumeCat2Idx["shirt"]] = entry.shirt;
				costume[level.costumeCat2Idx["head"]] = entry.head;
				costume[level.costumeCat2Idx["pants"]] = entry.pants;
				costume[level.costumeCat2Idx["eyewear"]] = entry.eyewear;
				costume[level.costumeCat2Idx["hat"]] = entry.hat;
				costume[level.costumeCat2Idx["kneepads"]] = entry.kneepads;
				costume[level.costumeCat2Idx["gloves"]] = entry.gloves;
				costume[level.costumeCat2Idx["shoes"]] = entry.shoes;
				costume[level.costumeCat2Idx["gear"]] = entry.gear;
				costume[level.costumeCat2Idx["exo"]] = entry.exo;

				perks = [];
				if ( isDefined( wildcard1 ) )
					perks[wildcard1] = true;
				if ( isDefined( wildcard2 ) )
					perks[wildcard2] = true;
				if ( isDefined( wildcard3 ) )
					perks[wildcard3] = true;
				
				if ( !isdefined(level.xuid2ownerId[entry.xuid]) && all_avatars_scheduled_for_delete() )
				{	// We want to reuse one scheduled for delete
					reuse_avatar( entry.xuid );
				}
				if (!isdefined(level.xuid2ownerId[entry.xuid]))
				{	// need to spawn the avatar
					ownerId = add_avatar( entry.xuid );
					vlprint( "PartyMemberClassChange "+entry.xuid+" : " +primaryWeapon+","+secondaryWeapon+","+lethal+","+tactical+"\n");
					SetDvar( "virtuallobbymembers", level.xuid2ownerId.size );
					spawnpoint = maps\mp\gametypes\vlobby::getSpawnPoint( ownerId );
					// since we don't know teams, we put them all on the same team...
					spawn_an_avatar( spawnpoint, primaryWeapon, secondaryWeapon, weaponName, lethal, tactical, perks, costume, entry.name, ownerId, false );
					avatar_after_spawn( ownerId );
					setEntPlayerXuidForEmblem( level.vlavatars[ownerId], entry.xuid );
					level.vlavatars[ownerId].loadout = entry;
					level.vlavatars[ownerId].membertimeout = GetTime() + 4000;

					if ( level.vlavatars.size == 1 )
					{
						// ensure CaO agent is in synch for whenever we go back to CaO
						caoAgent = level.players[0].cao_agent;
						vl_avatar_costume( caoAgent, level.vlavatars[ownerId].costume );
						costumeFound = caoAgent maps\mp\gametypes\_teams::playerCostume();
					}
				//	if (ownerid == 0)
				//	{
				//		level.players[0].spawned_avatar.preview_prim_weap_num = entry.primary;
				//	}
					
				}
				else
				{	// change the avatar's loadout
					ownerId = level.xuid2ownerId[entry.xuid];
					avatar = level.vlavatars[ownerId];
					
					if ( entry.player_controller >= 0 )		//(ownerId == 0)
					{
						if ( level.vl_local_focus == ownerId )
						{
							//vlprint( "PartyMemberClassChange update_local_class "+entry.xuid+" slot="+level.players[0].currentSelectedClass + " : " +primaryWeapon+","+secondaryWeapon+","+lethal+","+tactical+"\n");
							level.players[0] update_local_class( primary, primaryAttachment1, primaryAttachment2, primaryAttachment3, primaryCamoName, primaryReticleName, secondary, secondaryAttachment1, secondaryAttachment2, secondaryCamoName, secondaryReticleName, lethal, tactical, wildcard1, wildcard2, wildcard3, perk1, perk2, perk3, perk4, perk5, perk6 );
						}
					
						if ( IsDefined( avatar.previewCostume ) )
						{
							if ( isdefined( avatar.costumes ) && isdefined( avatar.costumes[ avatar.previewCostume ] ) )
								costume = avatar.costumes[ avatar.previewCostume ];
							else
							{
								print( "ownerId " + ownerId + " doesnt have a costume " + avatar.previewCostume );
								costume = level.players[0] maps\mp\gametypes\_class::cao_getCostumeByIndex( avatar.previewCostume );
							}
							costume = setDefaultCostumeIfNeeded( costume );
							// if a particular costume is bad, ensure it is fixed.
							if ( !isdefined(avatar.costumes) )
								avatar.costumes = [];
							avatar.costumes[avatar.previewCostume] = costume;
							
							if ( avatar.previewGearCategory != "none" )
							{
								catIdx = level.costumeCat2Idx[avatar.previewGearCategory];
								costume[catIdx] = avatar.previewGearId;
							}
						}
					}
					
					if ( loadout_changed( avatar.loadout, entry ) || costume_changed( avatar.costume, costume ) || ( IsDefined( level.forceAvatarRefresh ) && level.forceAvatarRefresh ) )
					{
						vlprint( "Updating xuid "+entry.xuid+" with ownerId="+ownerId+"\n");
						vlprint( "PartyMemberClassChange "+entry.xuid+" : " +primaryWeapon+","+secondaryWeapon+","+lethal+","+tactical+"\n");
						if ( IsDefined( avatar.player ) )
							avatar.player.costume = costume;	// this may need to wait for a commit
						vl_avatar_loadout( avatar.player, ownerid, primaryWeapon, secondaryWeapon, weaponName, lethal, tactical, perks, costume );
						if(entry.player_controller >= 0)
						{	// update the cao agent
							vl_avatar_loadout( avatar.player, ownerid, undefined, secondaryWeapon, weaponName, lethal, tactical, perks, costume, level.players[0].cao_agent );
						}
						avatar.loadout = entry;
						level.forceAvatarRefresh = false;
					}
				}
			}
			level.mccqueue = [];
			update_avatars();
			wait 0.05;
		}
	}
}

override_member_loadout_for_practice_round( entry )
{
	if ( !IsDefined( level.practice_round_costume ) )
	{
		level.practice_round_max_costumes = TableGetColumnCount( level.practiceRoundCostumeTableName ) - 1;
		level.practice_round_costume = RandomInt( level.practice_round_max_costumes );
	}
	
	if ( !IsDefined( level.practice_round_class ) )
	{
		practice_round_max_classes = TableGetColumnCount( level.practiceRoundClassTableName ) - 1;
		level.practice_round_class = RandomInt( practice_round_max_classes );
	}

	/#
	SetDevDvarIfUninitialized( "scr_test_pr_classes", 0 );
	if ( GetDvarInt( "scr_test_pr_classes", 0 ) )
	{
		time_per_char = 2000;
		time_total = level.practice_round_max_costumes * time_per_char;
		level.practice_round_costume = Int( ( GetTime() % time_total ) / time_per_char );
		level.practice_round_costume = level.practice_round_costume % level.practice_round_max_costumes;
	}
	#/
	
	assert( level.practice_round_costume < level.practice_round_max_costumes );
	
	newEntry = entry;
	
	// Get a practice round costume and put it in the entry
	newCostume = GetCostumeFromTable( level.practiceRoundCostumeTableName, level.practice_round_costume + 1 );

	newEntry.gender 	= newCostume[ level.costumeCat2Idx["gender"] ];
	newEntry.shirt 		= newCostume[ level.costumeCat2Idx["shirt"] ];
	newEntry.head 		= newCostume[ level.costumeCat2Idx["head"] ];
	newEntry.pants 		= newCostume[ level.costumeCat2Idx["pants"] ];
	newEntry.eyewear 	= newCostume[ level.costumeCat2Idx["eyewear"] ];
	newEntry.hat 		= newCostume[ level.costumeCat2Idx["hat"] ];
	newEntry.gear 		= newCostume[ level.costumeCat2Idx["gear"] ];
	newEntry.kneepads 	= newCostume[ level.costumeCat2Idx["kneepads"] ];
	newEntry.gloves 	= newCostume[ level.costumeCat2Idx["gloves"] ];
	newEntry.shoes 		= newCostume[ level.costumeCat2Idx["shoes"] ];
	newEntry.exo 		= newCostume[ level.costumeCat2Idx["exo"] ];
	
	// Give everyone weapons from the practice round classes
	classIndex = level.practice_round_class;
	classColumn = classIndex + 1;

	// Primary Weapon	
	primaryWeapon 		= TableLookup( level.practiceRoundClassTableName, 0, "loadoutPrimary", classColumn );
	primaryAttach1 		= TableLookup( level.practiceRoundClassTableName, 0, "loadoutPrimaryAttachment", classColumn );
	primaryAttach2 		= TableLookup( level.practiceRoundClassTableName, 0, "loadoutPrimaryAttachment2", classColumn );
	primaryAttach3 		= TableLookup( level.practiceRoundClassTableName, 0, "loadoutPrimaryAttachment3", classColumn );
	primaryCamo 		= TableLookup( level.practiceRoundClassTableName, 0, "loadoutPrimaryCamo", classColumn );
	primaryReticle		= TableLookup( level.practiceRoundClassTableName, 0, "loadoutPrimaryReticle", classColumn );
	
	newEntry.primary 			= Int( TableLookup( "mp/statstable.csv", 4, primaryWeapon, 0 ) );
	newEntry.primaryattachment1	= Int( TableLookup( "mp/attachmenttable.csv", 3, primaryAttach1, 0 ) );
	newEntry.primaryattachment2 = Int( TableLookup( "mp/attachmenttable.csv", 3, primaryAttach2, 0 ) );
	newEntry.primaryattachment3 = Int( TableLookup( "mp/attachmenttable.csv", 3, primaryAttach3, 0 ) );
	newEntry.primarycamo 		= Int( TableLookup( "mp/camoTable.csv", 1, primaryCamo, 0 ) );
	newEntry.primaryreticle 	= Int( TableLookup( "mp/reticleTable.csv", 1, primaryReticle, 0 ) );

	// Secondary Weapon
	secondaryWeapon		= TableLookup( level.practiceRoundClassTableName, 0, "loadoutSecondary", classColumn );
	secondaryAttach1 	= TableLookup( level.practiceRoundClassTableName, 0, "loadoutSecondaryAttachment", classColumn );
	secondaryAttach2 	= TableLookup( level.practiceRoundClassTableName, 0, "loadoutSecondaryAttachment2", classColumn );
	secondaryCamo 		= TableLookup( level.practiceRoundClassTableName, 0, "loadoutSecondaryCamo", classColumn );
	secondaryReticle	= TableLookup( level.practiceRoundClassTableName, 0, "loadoutSecondaryReticle", classColumn );
	
	newEntry.secondary 				= Int( TableLookup( "mp/statstable.csv", 4, secondaryWeapon, 0 ) );
	newEntry.secondaryattachment1 	= Int( TableLookup( "mp/attachmenttable.csv", 3, secondaryAttach1, 0 ) );
	newEntry.secondaryattachment2 	= Int( TableLookup( "mp/attachmenttable.csv", 3, secondaryAttach2, 0 ) );
	newEntry.secondarycamo 			= Int( TableLookup( "mp/camoTable.csv", 1, secondaryCamo, 0 ) );
	newEntry.secondaryreticle 		= Int( TableLookup( "mp/reticleTable.csv", 1, secondaryReticle, 0 ) );
		
	// Wildcards
	wildcard1 			= TableLookup( level.practiceRoundClassTableName, 0, "loadoutWildcard1", classColumn );
	wildcard2 			= TableLookup( level.practiceRoundClassTableName, 0, "loadoutWildcard2", classColumn );
	wildcard3 			= TableLookup( level.practiceRoundClassTableName, 0, "loadoutWildcard3", classColumn );
		
	newEntry.wildcard1 	= Int( TableLookup( "mp/perktable.csv", 1, wildcard1, 0 ) );
	newEntry.wildcard2 	= Int( TableLookup( "mp/perktable.csv", 1, wildcard2, 0 ) );
	newEntry.wildcard3 	= Int( TableLookup( "mp/perktable.csv", 1, wildcard3, 0 ) );

	return newEntry;
}

add_party_member_class_change( entry )
{
	if ( GetDvarInt( "practiceroundgame" ) )
		entry = override_member_loadout_for_practice_round( entry );
	
	// There should only be one entry on the queue for any given xuid
	for ( i=0; i<level.mccqueue.size; i++)
	{
		if ( level.mccqueue[i].xuid == entry.xuid )
		{
			level.mccqueue[i] = entry;
			entry = undefined;
			break;
		}
	}
	if ( IsDefined( entry ) )
	{	// add a new one.
		level.mccqueue[level.mccqueue.size] = entry;
	}
}


party_members( loadouts )
{
	if ( !IsDefined(level.xuid2ownerId) )
		return;	// ignore these calls until we've spawned the local player
	
	// reset the timeouts for all members that we have avatars for
	foreach ( loadout in loadouts )
	{
		xuid = loadout.xuid;
		ownerId = level.xuid2ownerid[xuid];
		if ( IsDefined( ownerId ) )
		{
			avatar = level.vlavatars[ ownerId ];
			if ( IsDefined( avatar ) )
			{
				avatar.membertimeout = GetTime() + 2000;
				avatar.memberHasTimedOut = undefined;
			}
		}
		if ( loadout.primary >= 0 )
		{
			add_party_member_class_change( loadout );
		}
	}
}

monitor_member_timeouts()
{
	while (true)
	{
		localplay = GetDvarInt( "splitscreen", 0 );
		online = IsOnlineGame();
		systemlink = IsSystemLink();
		foreach (ownerId, avatar in level.vlavatars)
		{
			if ( avatar_scheduled_for_removal( ownerId ) )
				continue;
			//if ( ( ownerId == 0 ) && ( online || systemlink ) )
			//	continue; // Always require avatar 0 in online and systemlink, localplay or mainmenu allows there to be no avatars
			if (avatar.membertimeout >= 0)
			{
				if (avatar.membertimeout < GetTime())
				{	// this member hasn't sent us an update, so assume they're not in the party anymore
					if ( ownerId == 0 && !IsDefined( avatar.memberHasTimedOut ) )
					{	// require two timeouts for ownerId 0
						avatar.membertimeout = GetTime() + 2000;
						avatar.memberHasTimedOut = 1;
						continue;
					}
					vlprint( "Schedule removal of ownerId "+ownerid + " from timeout\n");
					schedule_remove_avatar( ownerid );	// note that this doesn't change the level.vlavatars directly
				}
			}
		}
		wait 0.05;
	}
}

get_e3_costume( idx )
{
	table = "mp/E3CostumeTable.csv";
	costume = [];
	for (i = 0; i<level.costumeCat2Idx.size; i++)
	{
		costume[i] = int(TableLookupByRow( table, i+1, idx+1 ));
	}
	return costume;
}

monitor_debug_addfakemembers( player, camParams )
{
	/#
	while ( true )
	{
		if ( IsDefined( level.vl_fakemembers ) && level.vl_fakemembers > 0 && !IsDefined( level.debug_fly ) )
		{
			if(isdefined(camParams.mode) && camParams.mode == "game_lobby")
			{
				if ( player AdsButtonPressed() )
				{
					ownerId = level.vl_focus-1;
					if (ownerId < 0)
						ownerId = 17;
					oId = -1;
					while ( ownerId != oId )
					{
						foreach ( oId in level.xuid2ownerId )
						{
							if (oId == ownerId)
								break;
						}
						if (oId != ownerId)
						{
							oId = -1;
							ownerId--;
							if (ownerId < 0)
								ownerId = 17;
						}
					}
					level.vl_focus = ownerId;
					while ( player AdsButtonPressed() )
						wait 0.05;
				}
				else if ( player AttackButtonPressed() )
				{
					ownerId = level.vl_focus+1;
					if (ownerId > 17)
						ownerId = 0;
					oId = -1;
					while ( ownerId != oId )
					{
						foreach ( oId in level.xuid2ownerId )
						{
							if (oId == ownerId)
								break;
						}
						if (oId != ownerId)
						{
							oId = -1;
							ownerId++;
							if (ownerId > 17)
								ownerId = 0;
						}
					}
					level.vl_focus = ownerId;
					while ( player AttackButtonPressed() )
						wait 0.05;
				}
			}
		}
		if ( GetDvarInt( "scr_vl_addfakemembers", 0 ) > 0 )
		{
			xuid = ToString(GetTime());
			ownerId = add_avatar( xuid );
			if ( !IsDefined( level.fake_ownerids ) )
				level.fake_ownerids = [];
			level.fake_ownerids[level.fake_ownerids.size] = ownerId;
			vlprint( "Adding new fake xuid "+xuid+" with ownerId="+ownerId+"\n");
			vlprint( "PartyMemberClassChange "+xuid+" : " +player.primaryWeapon+","+player.secondaryWeapon+","+player.loadoutOffhand+","+player.loadoutEquipment+"\n");
			//SetDvar( "virtuallobbymembers", level.xuid2ownerId.size );
			spawnpoint = maps\mp\gametypes\vlobby::getSpawnPoint( ownerId );
			// since we don't know teams, we put them all on the same team...
			team = player.team;
			sessionTeam = player.sessionteam;
			name = "fakeplayer"+ownerId;
			//costume = maps\mp\gametypes\_teams::getDefaultCostume();
			costume = get_e3_costume( ownerid );
			spawn_an_avatar( spawnpoint, player.primaryWeapon, player.secondaryWeapon, player.pers["primaryWeapon"], player.loadoutEquipment, player.loadoutOffhand, player.perks, costume, name, ownerId, false );
			avatar_after_spawn( ownerId );
			avatar = level.vlavatars[ownerid];
			avatar.loadout = level.vlavatars[0].loadout;
			avatar.costume = costume;
			avatar.sessionCostume = avatar.costume;
			avatar SetCostumeModels(avatar.costume);
			avatar.membertimeout = GetTime() + 3600000;
			SetEntPlayerXuidForEmblem( avatar, xuid );
			level.vl_fakemembers++;
			SetDvar( "scr_vl_addfakemembers", toString( GetDvarInt( "scr_vl_addfakemembers", 1 ) - 1 ) );
		}
		if ( GetDvarInt( "scr_vl_addfakemembers", 0 ) < 0 )
		{
			foreach ( oId in level.fake_ownerids )
			{
				level.vlavatars[oId].membertimeout = GetTime() + 1000;
				level.vl_fakemembers--;
			}
			level.fake_ownerids = [];
			SetDvar( "scr_vl_addfakemembers", "0" );
			level.vl_focus = 0;
		}
		wait 0.05;
	}
	#/
}

monitor_member_focus_change()
{
	self endon("disconnect");
	
	while (true)
	{
		self waittill( "luinotifyserver", channel, xuid );
		if (channel == "member_select")
		{
			level.vl_focus = level.xuid2ownerId[xuid];
			if (!IsDefined(level.vl_focus))
			{
				vlprint( "vl_focus undefined, setting to 0\n");
				level.vl_focus = 0;
			}
			vlprint( "selected member " + xuid + " ownerId=" + level.vl_focus + "\n" );
		}
		if ( channel == "vlpresentable" )
		{
			vlprint( "in main menu\n" );
			thread SetVirtualLobbyPresentable();
		}
		if (channel == "leave_lobby")
		{	// xuid leaves lobby
			vlprint( "leave_lobby xuid="+xuid + "\n");
			if ( xuid == "0" )
			{
				foreach ( xuid, ownerId in level.xuid2ownerId )
				{
					vlprint( "Schedule removal of ownerId "+ownerid + "\n");
					schedule_remove_avatar( ownerid, 0.25 ); // wait same time we wait to clear virtualLobbyPresentable
				}
			}
			else
			{
				ownerId = level.xuid2ownerId[xuid];
				if ( IsDefined( ownerId ) )
				{
					vlprint( "Schedule removal of ownerId "+ownerid + "\n");
					schedule_remove_avatar( ownerid, 0.25 ); // wait same time we wait to clear virtualLobbyPresentable
				}
			}
			thread ResetVirtualLobbyPresentable();
		}
	}
}

monitor_cao_set_cao_focus( controller )
{
	if ( controller < 0 )		// closing cao
		level.vl_cao_focus = undefined;			
	else				// opening cao
	{
		level.vl_local_focus = GetFocusFromController( controller );
		level.vl_cao_focus = 1;
		vlprint( "cao ctrl = " + controller + " focus = " + level.vl_local_focus + "\n" );
	}
}

cao_set_costumes_from_lua( val )
{
	// controller|activeCostume|costumeIdx,...gearids...|costumeIdx,....#controller2|......

	vlprintln( "Cao set costumes from lua:" + val );
	cntrGrps = StrTok( val, "#" );
	foreach ( cntrGrp in cntrGrps )
	{
		costdefs = StrTok( cntrGrp, "|" );
		if ( costdefs.size > 0 )
		{
			controller = int( costdefs[0] );
			focus = GetFocusFromController( controller );
			avatar = level.vlavatars[focus];
			if ( IsDefined( avatar ) )
			{
				avatar.activeCostume = int( costdefs[1] );
				for ( i = 2; i < costdefs.size; i++ )
				{
					tokens = StrTok( costdefs[i], "," );
					costume = [];
					assert( tokens.size == ( level.costumeCat2Idx.size + 1 ) );
					for ( type=1; type < tokens.size; type++ )
						costume[costume.size] = int(tokens[type]);
					if ( !isdefined( avatar.costumes ) )
						avatar.costumes = [];
					avatar.costumes[int(tokens[0])] = costume;
				}
			}
		}
	}
}

monitor_cao_set_costume_preview( val )
{
	basetoks = StrTok( val, ":" );
	controller = int(basetoks[0]);
	tokens = StrTok( basetoks[1], "," );
	ownerid = GetFocusFromController( controller );
	level.vl_local_focus = ownerid;
	avatar = level.vlavatars[ownerid];
	if (isdefined( avatar ))
	{
		avatar.previewCostume = Int( tokens[0] );
		avatar.previewGearCategory = tokens[1];
		avatar.previewGearId = Int( tokens[2] );
	}
	return avatar;
}

monitor_create_an_operator( ownerid )
{
	self endon("disconnect");
	
	while (true)
	{
		self waittill( "luinotifyserver", channel, val );
		if (channel == "cao")
		{
			monitor_cao_set_cao_focus( val );
			
			level.caoAvatarPosOffset = undefined;
			
			if ( IsDefined(level.vl_local_focus) )
				avatar = level.vlavatars[level.vl_local_focus];
			else
				avatar = level.vlavatars[ownerid];
			if ( val < 0 && IsDefined(avatar) )
			{
				avatar.previewGearCategory = undefined;
				avatar.previewGearId = undefined;
				avatar.previewCostume = undefined;
				if ( IsDefined(avatar.costumes) )
					vl_avatar_costume( avatar, avatar.costumes[avatar.activeCostume], true );
			}
		}
		else if (channel == "lootscreen_gear_highlighted")
		{
			// gear loot focused on loot screen
			
			level.cac = undefined;
			// instead of waiting for level.cac = undefined to take effect on the 
			// next 'vlobby_player' update,	turn off cac mode immediately in case we've just come
			// from a loot weapon focus, in which case the next 'vlobby_player' needs to handle 
			// turning on cao mode
		//	vlobby_switch_off_cac( level.weaponAvatar );
			
			monitor_cao_set_cao_focus( val );
			if ( IsDefined( level.vl_cao_focus ) )
			{
				level.caoAvatarPosOffset = ( 56, 0, 5 );
			}
			else
			{
				level.caoAvatarPosOffset = undefined;
			}
			
			// keep cached camera mode to 'prelobby_loot' when browsing on the loot screen, 
			// we don't want to store 'cac'/'cao' cam modes since we're temporarily switching 
			// among them
			// TODO Do we need this push mode bellow?  I don't see it breaking anything, yet..
			level.camParams.pushmode = "prelobby_loot";
		}
		else if ( channel == "costumes" )
		{
			cao_set_costumes_from_lua( val );
		}
		else if ( channel == "costume_preview" )
		{
			monitor_cao_set_costume_preview( val );
		}
		else if ( channel == "costume_apply" )
		{
			avatar = monitor_cao_set_costume_preview( val );
			
			if (IsDefined(avatar))
			{
				// This is now being done in lua, so we can ensure local clients are updated correctly.
				catIdx = level.costumeCat2Idx[avatar.previewGearCategory];
				if ( isdefined( avatar.costumes ) && isdefined( avatar.costumes[avatar.previewCostume] ) )
				{
					avatar.costumes[avatar.previewCostume][catIdx] = avatar.previewGearId;
					if ( avatar.previewCostume == avatar.activeCostume )
					{
						avatar SetCostumeModels( avatar.costumes[avatar.previewCostume] ); // update the displayed
					}
				}
			}
		}
	}
}

monitor_clans()
{
	self endon("disconnect");
	level.clanMemberOwnerIDs = [];

	while (true)
	{
		self waittill( "luinotifyserver", channel, val );

		if (channel == "clanprofile")
		{
			vlprint( "Using clan profile VL settings\n" );
			if ( val < 0 )	
				level.vl_clanprofile_focus = undefined;	
			else
				level.vl_clanprofile_focus = val;
			level.caoAvatarPosOffset = undefined;

			//Functionality has been removed for TU1. For a later TU we'll be spawning the 3 member highlights and setting
			//their equipment appropriately
		}
	}
}

hide_avatar( avatar )
{
	avatar Hide();
	avatar HideAllParts();

	hide_avatar_weapons( avatar );
}

show_avatar( avatar )
{
	AddLaunchers(avatar);
	avatar Show();
	avatar ShowAllParts();
	
	if ( isDefined( avatar.primaryWeaponEnt ) )
	{
		avatar.primaryWeaponEnt Show();
		avatar.primaryWeaponEnt ShowAllParts();
	}
	if ( isDefined( avatar.secondaryWeaponEnt ) )
	{
		avatar.secondaryWeaponEnt Show();
		avatar.secondaryWeaponEnt ShowAllParts();
	}
	if ( isDefined( avatar.akimboWeaponEnt ) && isDefined( avatar.akimboWeaponEnt.primaryWeapon ))
	{
		if( IsSubStr( avatar.akimboWeaponEnt.primaryWeapon, "akimbo" ) || IsSubStr( avatar.akimboWeaponEnt.primaryWeapon, "akimboxmg"))
	{
		avatar.akimboWeaponEnt show();
		avatar.akimboWeaponEnt ShowAllParts();
	}
	}
//	level.forceAvatarRefresh = true;
}


hide_avatar_primary_weapon( avatar )
{
	if ( isDefined( avatar.primaryWeaponEnt ) )
	{
		avatar notify( "hide_primary_weapon" );
		avatar.primaryWeaponEnt Hide();
		avatar.primaryWeaponEnt HideAllParts();
	}
}

hide_avatar_secondary_weapon( avatar )
{
	if ( isDefined( avatar.secondaryWeaponEnt ) )
	{
		avatar notify( "hide_secondary_weapon" );
		avatar.secondaryWeaponEnt Hide();
		avatar.secondaryWeaponEnt HideAllParts();
	}
}

hide_avatar_akimbo_weapon( avatar )
{
	if ( isDefined( avatar.akimboWeaponEnt ) )
	{
		avatar notify( "hide_akimbo_weapon" );
		avatar.akimboWeaponEnt Hide();
		avatar.akimboWeaponEnt HideAllParts();
	}
}

hide_avatar_weapons( avatar )
{
	hide_avatar_primary_weapon( avatar );
	hide_avatar_secondary_weapon( avatar );
	hide_avatar_akimbo_weapon( avatar );
}

hide_avatars()
{
	foreach (avatar in level.vlavatars)
		hide_avatar( avatar );
}

show_avatars()
{
	foreach (avatar in level.vlavatars)
		show_avatar( avatar );
}

get_xuid_for_avatar( avatar )
{
	foreach (xuid, ownId in level.xuid2ownerId)
	{
		if (isdefined( level.vlavatars[ownId] ) && level.vlavatars[ownId] == avatar )
			return xuid;
	}
	return "";
}

remove_avatar( ownerId )
{
	ownId = -1;
	foreach (xuid, ownId in level.xuid2ownerId)
	{
		if (ownId == ownerId)
			break;
	}
	vlprint( "Removing xuid "+xuid+" for ownerId "+ownerId + "\n");
	assert(ownId == ownerId);
	setEntPlayerXuidForEmblem( level.vlavatars[ownerid], xuid, true );	// release this xuid
	level.xuid2ownerId[xuid] = undefined;
	level.avatarInfo[ownerId].timeToDelete = 0;
	level.avatarInfo[ownerId].avatar = undefined;
	SetDvar( "virtuallobbymembers", level.xuid2ownerId.size );
	agent = level.vlavatars[ownerId];
	level.vlavatars[ownerId] = undefined;
	if ( level.vl_focus == ownerId )
	{
		if ( level.vlavatars.size > 0 )
		{
			foreach ( idx, avatar in level.vlavatars )
			{
				level.vl_focus = idx;
				break;
			}
		}
	}
	hide_avatar( agent );
	agent TakeAllWeapons();
	agent DetachAll();
	agent.headModel = undefined;
	agent HideAllParts();
	if(isdefined(agent.avatar_spawnpoint.spawned_avatar))
	{
		agent.avatar_spawnpoint.spawned_avatar = undefined;
	}
	if ( isdefined(agent.primaryWeaponEnt) )
	{
		setEntPlayerXuidForEmblem( agent.primaryWeaponEnt, xuid, true );	// release this xuid
		agent.primaryWeaponEnt Delete();
		agent.primaryWeaponEnt = undefined;
	}
	if ( isdefined(agent.secondaryWeaponEnt) )
	{
		setEntPlayerXuidForEmblem( agent.secondaryWeaponEnt, xuid, true );	// release this xuid
		agent.secondaryWeaponEnt Delete();
		agent.secondaryWeaponEnt = undefined;
	}
	if ( isDefined( agent.akimboWeaponEnt ) )
	{
		setEntPlayerXuidForEmblem( agent.akimboWeaponEnt, xuid, true );	// release this xuid
		agent.akimboWeaponEnt delete();
		agent.akimboWeaponEnt = undefined;
	}
	agent.primaryWeapon = undefined;
	agent.stored_weapon = undefined;
	free_avatar( agent );
	if ( level.vl_focus == ownerid )
		level.vl_focus = 0;	// back to the local player
}

reset_bot_settings_for_a_few_frames()
{
	level notify("stop_reset_bot_settings");
	level endon("stop_reset_bot_settings");
	for (i=0; i<2; i++)
	{
		// turning off animstate resets some settings
		self maps\mp\agents\_agent_common::set_agent_health( 100 );
		self BotClearScriptEnemy();
		self BotClearScriptGoal();
		self bot_disable_tactical_goals();
		self BotSetFlag("disable_movement", 1);
		self BotSetFlag("disable_rotation", 1);
		wait 0.05;
	}
}
HackAgentAngles(angles)
{
	self SetPlayerAngles(angles);
}

#using_animtree( "multiplayer_vlobby" );
use_animstate( animAlias, duringSetup, animstate )
{
	RandomPlaybackRate = RandomFloatRange(0.85, 1.15);
	enablechanged = false;
	if (!IsDefined(self.animAlias))
	{
		self SetAnimClass( "vlobby_animclass" );
		enablechanged = true;
		level notify("stop_reset_bot_settings");	// kill if still active
		self HackAgentAngles(self.spawn_angles);
	}
		
//	AnimEntry = self GetAnimEntry("lobby_idle_energy", 0);
//	count = self GetAnimEntryCount("lobby_idle");
	if(!IsDefined(animstate))
	{
		animstate = "lobby_idle";
		animAlias = "assault_pose_06";
	}
	self.animAlias = animAlias;
	self.animstate = animstate;
	self SetAnimState( animstate, animAlias, RandomPlaybackRate );
	if ( !IsDefined(duringSetup) || !duringSetup )
	{
		self.animAlias = animAlias;
		if (enablechanged)
			vl_give_weapons(self);
	}
}

#using_animtree( "multiplayer_vlobby" );
start_anim()
{
	if (isdefined(level.camParams) && isdefined(level.camParams.mode))
	{
		switch( level.camParams.mode )
		{
			case "prelobby":
			case "prelobby_loot":	// same start anim as "prelobby"
				animIdx=0;
				
				if (isdefined(level.vlavatars))
				{
					foreach( avatar in level.vlavatars )
					{	/*
						if ( avatar == self )
							break;
						animIdx++;
						if (animIdx >= level.num_lobby_idles)
							animIdx = 0;
						*/
						if(!IsDefined(avatar.animAlias))
						{
							position_num = avatar.avatar_spawnpoint FindPositionNum();
							if(isDefined(self.ownerid) && self.ownerid == 0) // forcing the player to get whatever anim we want / be random
							{
								animIdx = 8;
							}
							else if(position_num < level.num_lobby_idles)
							{
								animIdx = position_num;
							}
							else
							{
								animIdx++;
								if (animIdx >= level.num_lobby_idles)
								animIdx = 0;
							}
							animstate = "lobby_idle";
							self use_animstate( animIdx, undefined, animstate );
						}
					}
				}
				/*
				if(isDefined(self.ownerid) && self.ownerid == 0) // forcing the player to get whatever anim we want / be random
				{
				//	animIdx = RandomIntRange(0, level.num_lobby_idles - 1);
				}
				*/
				
				break;
			case "transition":
			case "game_lobby":
			case "cac":
			case "clanprofile":
				animIdx=0;
				if (isdefined(level.vlavatars))
				{
					foreach( avatar in level.vlavatars )
					{
						if(!IsDefined(avatar.animAlias))
						{
							position_num = avatar.avatar_spawnpoint FindPositionNum();
							if(isDefined(self.ownerid) && self.ownerid == 0) // forcing the player to get whatever anim we want / be random
							{
								animIdx = 8;
							}
							else if(position_num < level.num_lobby_idles)
							{
								animIdx = position_num;
							}
							else
							{
								animIdx++;
								if (animIdx >= level.num_lobby_idles)
								animIdx = 0;
							}
							animstate = "lobby_idle";
							self use_animstate( animIdx, undefined, animstate );
						}
					}
				}
				break;
			case "cao":
				if(!IsDefined(self.animAlias))
				{
					if(isDefined(self.ownerid) && self.ownerid == 0) // forcing the player to get whatever anim we want / be random
					{
						animIdx = 8;
					}
					else
					{
						animIdx = RandomIntRange(0, level.num_lobby_idles - 1); /// probably should never happen currently, we want to just use whatever anim was assigned in pr elobby or game lobby for now
					}
					animstate = "lobby_idle";
					self use_animstate( animIdx, undefined, animstate );
				}
				break;
			default:	// shouldn't happen, but handle it none the less.
				if(!IsDefined(self.animAlias))
				{
					if(isDefined(self.ownerid) && self.ownerid == 0) // forcing the player to get whatever anim we want / be random
					{
						animIdx = 8;
					}
					else
					{
						animIdx = RandomIntRange(0, level.num_lobby_idles - 1); /// probably should never happen currently, we want to just use whatever anim was assigned in pr elobby or game lobby for now
					}
					
					animstate = "lobby_idle";
					self use_animstate( animIdx, undefined, animstate );
				
				}
				break;
				
		}
	}
}

start_prelobby_anims()
{
	// Is this function still needed?
	if (!isdefined( level.vlavatars ) )
		return;
	
	foreach ( avatar in level.vlavatars )
	{
		if(!isdefined(avatar.animAlias) || !isdefined(avatar.animstate))
		{
			animdata = avatar GetAnimData(avatar.primaryweapon);
			avatar use_animstate( animdata.alias, undefined, animdata.animstate );
		}
	}
}

start_lobby_anims()
{
	// Is this function still needed?
	if (!isdefined( level.vlavatars ) )
		return;
	
	foreach ( avatar in level.vlavatars )
	{
		if(!isdefined(avatar.animAlias) || !isdefined(avatar.animstate))
		{
			animdata = avatar GetAnimData(avatar.primaryweapon);
			avatar use_animstate( animdata.alias, undefined, animdata.animstate );
		}
	}
}

start_cao_anims()
{
	// Is this function still needed?
	if (!isdefined( level.vlavatars ) )
		return;
	
	foreach ( avatar in level.vlavatars )
	{
		if(!isdefined(avatar.animAlias) || !isdefined(avatar.animstate))
		{
			animdata = avatar GetAnimData(avatar.primaryweapon);
			avatar use_animstate( animdata.alias, undefined, animdata.animstate );
		}
	}
}

//************************************************************************************************************
//**************  Procedural Camera **************************************************************************
//************************************************************************************************************

/*
 * returns an array of points to move a camera from start to end without colliding with obstacles
 * start, end are vectors
 * obstacles is an array with each entry being an array with the center and radius of the obstacle
 * 
 * 
 */
find_best_cam_path( _start, _end, obstacles )
{
	path = [];
	path[0] = _start;
	path[1] = _end;
	safetycount = 20;
	
	modified = true;
	while ( modified && ( safetycount > 0 ) )
	{
		safetycount--;
		modified = false;
		newpath = [];
		newpath[newpath.size] = path[0];
		bail = false;
		bailidx = 0;
		for ( i=0; ( i< (path.size-1) ) && !bail; i++ )
		{	// for each subpath
			collision = true;
			start = path[i];
			end = path[i+1];
			while (collision)
			{
				collision = false;
				best = undefined;
				best_trace = undefined;
				foreach ( obstacle in obstacles )
				{
					trace = trace2d( start, end, obstacle );
					if ( trace["intersect"] )
					{
						if ( ( distance2D( newpath[newpath.size-1], trace["closestpoint"] ) > 0.1 ) &&
						    ( distance2D( end, trace["closestpoint"] ) > 0.1 ) )
						{	// ignoring new closestpoints too close to original points
							collision = true;
							if ( !IsDefined(best_trace) || ( best_trace[ "radratio" ] > trace["radratio"] ) )
							{	// pick the intersection that is closest to the center of the circle
								best = obstacle;
								best_trace = trace;
							}
						}
					}
				}
				if ( collision )
				{	// use the best intersection to split the line
					modified = true;
					// if the best intersection is close to an endpoint, we need to move the endpoint, which means we need to break all the way out
					if ( length2D( start, best["center"] ) < best[ "radius" ] )
					{	// need to move start
						start = move_outside_circle( start, best );
						assert( i > 0 );	// can't change original starting point
						newpath[newpath.size-1] = start;	// modify original position
						collision = false; // force bail out of this loop
						bail = true;		// force bail of outer loop
						bailidx = i+1;
					}
					else if ( length2D( end, best["center"] ) < best[ "radius" ] )
					{	// need to move end
						end = move_outside_circle( end, best );
						assert( (i+1) < path.size );	// can't change original ending point
						newpath[newpath.size] = end;	// modify original position
						collision = false; // force bail out of this loop
						bail = true;		// force bail of outer loop
						bailidx = i+2;
					}
					else
					{	// subdivide ( note that start is already on newpath )
						newpath[newpath.size] = best_trace["closestpoint"];
						newpath[newpath.size] = end;
						collision = false;	// since we've added entries, we need to stop testing here (no loop needed)
					}
				}
				else
				{	// just add the end point to newpath
					newpath[newpath.size] = end;
				}
			}
		}
		if ( bail )
		{	// add the remainder of path to newpath, since we modified an internal path element
			for ( i = bailidx; i < path.size; i++ )
			{
				newpath[newpath.size] = path[i];
			}
		}
		path = newpath;
	}
	return path;
}

move_outside_circle( point, circle )
{
	center = circle["center"];
	radius = circle["radius"];
	dir = VectorNormalize( ( point[0] - center[0], point[1] - center[1], 0 ) );
	point = (center[0] + radius*dir[0], center[1] + radius*dir[1], point[2] );
	return point;
}

trace2d( start, end, circle )
{
	pathbias = OBSTACLE_PATHBIAS;
	center = circle["center"];
	radius = circle["radius"];
	pathradius = radius + pathbias;
	vec2d = ( end[0] - start[0], end[1] - start[1], 0 );
	dir = VectorNormalize( vec2d );
	dist = Length2D( vec2d );
	s2c = ( center[0] - start[0], center[1] - start[1], 0 );
	off = VectorDot( dir, s2c );
	if ( off < 0 )
		off = 0.0;
	else if ( off > dist )
		off = dist;
	closestpoint = ( start[0] + off*dir[0], start[1] + off*dir[1], 0 );
	fraction = off/dist;
	normal = ( closestpoint[0] - center[0], closestpoint[1] - center[1], 0 );
	dist = Length2D( normal );
	intersect = false;
	radratio = 1.0;
	if ( dist < radius )
	{
		intersect = true;
		normal = VectorNormalize( normal );
		// we'll pick a point that is a little further away so we don't re-intersect the same cylinder
		closestpoint = ( center[0] + pathradius*normal[0], center[1] + pathradius*normal[1], start[2] + (fraction * (end[2] - start[2])) );
		radratio = dist/radius;
	}
	retval = [];
	retval[ "intersect" ] = intersect;
	retval[ "fraction" ] = fraction;
	retval[ "closestpoint" ] = closestpoint;
	retval[ "radratio" ] = radratio;
	return retval;
}

calc_new_pos( prv_point, speed, goal )
{
	dist = Distance( prv_point, goal );
	if ( dist > speed )
	{
		dir = VectorNormalize( goal - prv_point );
		goal = prv_point + speed*dir;
	}
	return goal;
}

lookahead_path( prvIdx, prv_point, speed, lookahead, path )
{
	// find the closes point on the current segment
	start = path[prvIdx];
	if ( ( prvIdx + 1 ) >= path.size )
	{	// at end, so lookahead is the endpoint
		return [ prvIdx, calc_new_pos( prv_point, speed, path[prvIdx] ) ];
	}
	end = path[prvIdx+1];
	len = Distance( start, end );
	dir = VectorNormalize( end - start );
	dist_on_path = VectorDot( dir, prv_point-start );
	if (dist_on_path < 0)
		dist_on_path = 0;
	if (dist_on_path > len)
		dist_on_path = len;
	pnt_on_path = start + dist_on_path*dir;
	lookahead_left = lookahead;
	dist_in_seg = len - dist_on_path;
	lookahead_point = path[path.size-1];	// to handle fall through case
	while (lookahead_left > 0)
	{
		if ( dist_in_seg > lookahead_left )
		{
			lookahead_point = pnt_on_path + lookahead_left*dir;
			lookahead_left = 0;
		}
		else
		{	// at end, so lookahead is the endpoint
			if ( ( prvIdx + 1 ) >= path.size )
			{
				lookahead_point = path[prvIdx];
				lookahead_left = 0;
			}
			lookahead_left -= dist_in_seg;
			prvIdx++;
			pnt_on_path = path[prvIdx];
		}
	}
	return [ prvIdx, calc_new_pos( prv_point, speed, lookahead_point ) ];
}

init_path_traversal( camera )
{
	camera.distanceOnPath = 0;
	camera.pathIdx = 0;
	camera.distOnPathSegment = 0;
	camera.pathSpeed = 0;
	idealSpeed = camera.pathInfo_len/camera.PATH_IDEALTIME;
	if (idealSpeed < camera.PATH_MINSPEED)
		idealSpeed = camera.PATH_MINSPEED;
	if (idealSpeed > camera.PATH_MAXSPEED)
		idealSpeed = camera.PATH_MAXSPEED;
	camera.pathAccel = 0.05*idealSpeed/camera.PATH_ACCEL_TIME;
	camera.pathDeccel = 0.05*idealSpeed/camera.PATH_DECCEL_TIME;
	camera.pathTgtSpeed = idealSpeed;
	camera.pathAccelMode = 1;
	camera.pathInfo_t = 0;
	camera.pathInfo_totalt = camera.PATH_IDEALTIME;
	steps = (camera.PATH_DECCEL_TIME/0.05);
	dist = 0;
	speed = idealSpeed;
	// brute force calculation of distance travelled during decel
	for (i=0; i<steps; i++ )
	{
		speed -= camera.pathDeccel;
		if (speed < camera.PATH_MINDECCELSPEED)
			speed = camera.PATH_MINDECCELSPEED;
		dist += speed;
	}
	camera.pathStartDeccelDist = camera.pathInfo_len - dist;
	camera.pitch = camera.pathInfo_startPitch;
}

update_path_speed( camera )
{
	if (camera.distanceOnPath < camera.pathStartDeccelDist)
	{
		camera.pathSpeed += camera.pathAccel;
		if (camera.pathSpeed > camera.pathTgtSpeed)
			camera.pathSpeed = camera.pathTgtSpeed;
	}
	else
	{
		camera.pathSpeed -= camera.pathDeccel;
		if (camera.pathSpeed < camera.PATH_MINDECCELSPEED)
			camera.pathSpeed = camera.PATH_MINDECCELSPEED;
	}
}

init_path_constants( camera )
{
	camera.PATH_MAXSPEED = 0.05*300;
	camera.PATH_MINSPEED = 0.05*36;
	camera.PATH_MINDECCELSPEED = 0.05*6;
	camera.PATH_IDEALTIME = 0.5;
	camera.PATH_ACCEL_TIME = 0.15*camera.PATH_IDEALTIME;
	camera.PATH_DECCEL_TIME = 0.15*camera.PATH_IDEALTIME;
	camera.pathMaxRotZ = 0.05*90;
	camera.pathMaxRotX = 0.05*90;
}

build_path_info( camera, camParams, start_loc, end_loc, endAngles )
{
	
	path = find_best_cam_path( camera.origin, end_loc, camera.obstacles );
	camera.path = path;
	len = 0;
	foreach ( idx, node in camera.path )
	{
		if (idx > 0)
		{
			len += Distance( camera.path[idx], camera.path[idx-1] );
		}
	}
	camera.pathInfo_t = 0;
	camera.pathInfo_totalt = camera.PATH_IDEALTIME;
	camera.pathInfo_len = len;
	camera.pathInfo_startAim = AnglesToForward( camera.angles );
	camera.pathInfo_endAim = AnglesToForward( endAngles );
//	NewTargetPosition = FindNewTargetPos(endEnt.camera_helper.angles,endEnt.camera_helper.origin,endEnt.camera_lookat.origin);
	/*
	if ( IsDefined( camParams ) )
	{
		camera.pathInfo_endAim = calc_target_dir( endEnt.camera_helper.origin, NewTargetPosition, camParams );
	}
	else
	{
		camera.pathInfo_endAim = VectorNormalize( NewTargetPosition - endEnt.camera_helper.origin );
	}
	*/
	
	
	
//	endAngles = VectorToAngles( camera.pathInfo_endAim );
	camera.pathInfo_startPitch = AngleClamp180(camera.angles[0]);	// we will lerp the pitch to the endpitch
	camera.pathInfo_curStartPitch = camera.pathInfo_startPitch;		// tracks the moving pitch value to prevent seesawing
	camera.pathInfo_endPitch = AngleClamp180(endAngles[0]);
	camera.pathInfo_startYaw = AngleClamp180(camera.angles[1]);
	camera.pathInfo_endYaw = AngleClamp180(endAngles[1]);
	camera.pathInfo_start_loc = start_loc;
	camera.pathInfo_end_loc = end_loc;
	camera.pathInfo_velocity = (0,0,0);
	baseTravelDir = VectorNormalize( camera.pathInfo_end_loc - camera.pathInfo_start_loc );
	startDP = VectorDot( baseTravelDir, camera.pathInfo_startAim );
	endDP = VectorDot( baseTravelDir, camera.pathInfo_endAim );
	path_mode = 0;	// standard mode
	if ( ( endDP < -0.707 ) && ( startDP < -0.707 ) )
		path_mode = 1;		// leave camera pointint at old target until the new target is close to the fov
	camera.pathInfo_mode = path_mode;
	init_path_traversal( camera );
}
FindNewTargetPos(CamAngles,CamOrigin,OldTargetOrigin)
{
	//// trying to move the target to match the angles of the camera ent in radiant. 
	Vec3CamDir = AnglesToForward(CamAngles);
	Cam2Tgt = OldTargetOrigin - CamOrigin;
	Dist2Tgt = VectorDot(Cam2Tgt,Vec3CamDir);
	NewTargetPosition = CamOrigin + (Vec3CamDir * Dist2Tgt);

	return NewTargetPosition;
}

sigmoid( x, maxx )
{
	// x comes in (0-1)
	xoffset = -0.5;
	x += xoffset;	// now it is -0.5 to 0.5
	x *= 2*maxx;	// now it is -maxx to maxx
	scale = sqrt( 1 + maxx*maxx )/(2*maxx);	// used to keep the result from -0.5 to 0.5
	offset = 0.5;	// this will move the result to 0 to 1
	value = scale*( x/sqrt(1 + x*x) ) + offset;
	assert( value >= 0 && value <= 1 );
	return value;
}

lerp_towards_desiredAngle( camera, desiredAngles )
{
	desiredX = AngleClamp180(desiredAngles[0]);
	currentX = AngleClamp180(camera.angles[0]);
	
	dZ = AngleClamp180(desiredAngles[1] - camera.angles[1]);
	dX = AngleClamp180(desiredX - currentX);
	if ( dZ < -1*camera.pathMaxRotZ )
		dZ = -1*camera.pathMaxRotZ;
	if ( dZ > camera.pathMaxRotZ )
		dZ = camera.pathMaxRotZ;
	if ( dX < -1*camera.pathMaxRotX )
		dX = -1*camera.pathMaxRotX;
	if ( dX > camera.pathMaxRotX )
		dX = camera.pathMaxRotX;
	deltaAngles = ( dX, dZ, 0 );
	camera.angles = camera.angles + deltaAngles;
	if ((abs(dX) < 0.1) && (abs(dZ) < 0.1))
		return true;
	else
		return false;
}

update_camera_angles_on_path( camera, camParams )
{
	fraction = camera.pathInfo_t/camera.pathInfo_totalt;
	fraction = sigmoid( fraction, 2 );
	// One exception is if we are moving in a direction opposite our final direction
	desiredAngles = camera.angles;
	// we lerp our pitch between our start and end pitches.  If the desired pitch is closer to the end, then we take it
	lerpedPitch = fraction*(camera.pathInfo_endPitch - camera.pathInfo_startPitch) + camera.pathInfo_startPitch;
	// we lerp our yaw range between our start and end yaw.
	// startYaw moves to endYaw as we move along path
	startYaw = camera.pathInfo_startYaw + fraction*(camera.pathInfo_endYaw - camera.pathInfo_startYaw);
	desiredYaw = camera.angles[1] + AngleClamp180( startYaw - camera.angles[1] );
	desiredAngles = ( lerpedPitch, desiredYaw, desiredAngles[2] );
	
	return lerp_towards_desiredAngle( camera, desiredAngles );
}

update_camera_position_on_path( camera )
{
	fraction = camera.pathInfo_t/camera.pathInfo_totalt;
	fraction = sigmoid( fraction, 2 );
	distToTravel = fraction*camera.pathInfo_len;
	camera.pathIdx = 0;
	camera.distOnPathSegment = 0;
	camera.distanceOnPath = 0;
	while ( distToTravel > 0 )
	{
		curSegLen = Distance( camera.path[camera.pathIdx], camera.path[camera.pathIdx+1] );
		remainingLen = curSegLen - camera.distOnPathSegment;
		if (remainingLen > distToTravel)
		{
			camera.distOnPathSegment += distToTravel;
			neworigin = camera.path[camera.pathIdx] + camera.distOnPathSegment*VectorNormalize( camera.path[camera.pathIdx+1] - camera.path[camera.pathIdx] );
			camera.pathInfo_velocity = neworigin - camera.origin;
			camera.origin = neworigin;
			camera.distanceOnPath += distToTravel;
			distToTravel = 0;
		}
		else
		{
			distToTravel -= remainingLen;
			camera.pathIdx++;
			if (camera.pathIdx >= (camera.path.size - 1))
			{	// end of the path
				if ( camera.pathIdx < camera.path.size )
					neworigin = camera.path[camera.pathIdx];
				else
					neworigin = camera.path[camera.path.size-1];
				camera.pathInfo_velocity = neworigin - camera.origin;
				camera.origin = neworigin;
				camera.distanceOnPath = camera.pathInfo_len;
				distToTravel = 0;
			}
			else
			{
				camera.distanceOnPath += remainingLen;
				camera.distOnPathSegment = 0;
			}
		}
	}
}

update_camera_on_path( camera, camParams )
{
	finished = false;
	camera.pathInfo_t += 0.05;
	if ( camera.pathInfo_t >= camera.pathInfo_totalt )
	{
		camera.pathInfo_t = camera.pathInfo_totalt;
		finished = true;
		camera.pathInfo_velocity = (0,0,0);
	}
	//update_path_speed( camera );
	update_camera_position_on_path( camera );
	update_camera_angles_on_path( camera, camParams );
	return finished;
}

get_target_from_avatar( avatar )
{
	tag = "j_neck";
	target = avatar GetTagOrigin( tag );
	return target;
}

calc_f_from_avatar( avatar )
{
//	tag_camera_loc_vec3 = avatar GetTagOrigin("TAG_STOWED_BACK");
//	tag_camera_angles_vec3 = avatar GetTagAngles("TAG_STOWED_BACK");
	
	CameraData = RotateAvatarTagCamera(avatar);
	tag_camera_loc_vec3 = CameraData.camera_tag_origin;
	tag_camera_angles_vec3 = CameraData.camera_tag_angles;
	
	target = get_target_from_avatar( avatar );
	cam_origin = tag_camera_loc_vec3;
	cam_lookat = tag_camera_loc_vec3 + (anglestoforward(tag_camera_angles_vec3) * 64);
	// NewTargetPosition = FindNewTargetPos(avatar.avatar_spawnpoint.camera_helper.angles,cam_origin, cam_lookat);
	return calc_f( cam_origin, cam_lookat, target );
}

calc_f_from_avatar_spawnpoint( avatar_spawnpoint )
{
	// this won't get us he camera look at anymore.  Now based on animation bone location
	target = avatar_spawnpoint.origin;
	cam_origin = avatar_spawnpoint.camera_helper.origin;
	cam_lookat = avatar_spawnpoint.camera_lookat.origin;
	return calc_f( cam_origin, cam_lookat, target );
}

calc_f( cam_origin, cam_lookat, target )
{
	P = cam_lookat;
	C = cam_origin;
	T = target;
	result = [];
	X = VectorNormalize( VectorCross( P-C, (0,0,1) ) );
	Z = VectorNormalize( VectorCross( X, P-C ) );
	CT =T - C;
	Tx = T - (VectorDot( Z, CT )*Z);
	Tz = T - (VectorDot( X, CT )*X);
	Dx = Distance( Tx, P );
	Hx = Distance( Tx, C );
	fx = Dx/Hx;
	sx = 1;
	if ( VectorDot( CT, X ) < 0 )
	{
		sx = -1;
	}
	result["fx"] = fx;
	result["sx"] = sx;
	Dz = Distance( Tz, P );
	Hz = Distance( Tz, C );
	fz = Dz/Hz;
	sz = 1;
	if ( VectorDot( CT, Z ) < 0 )
	{
		sz = -1;
	}
	result["fz"] = fz;
	result["sz"] = sz;
	return result;
}

calc_f_fromscreen( ex, ez )
{
	FOV = GetDvarFloat("cg_fov", 45 ) * GetDvarFloat( "cg_fovScale", 1.0 );
	aspectz = 1.0;
	dfov = tan( FOV );
	result = [];

	dx = dfov * abs(ex);
	sx = 1;
	if ( ex < 0 )
		sx = -1;
	fx = dx / sqrt( 1 - dx*dx );
	result["sx"] = sx;
	result["fx"] = fx;
	
	dz = aspectz * dfov * abs(ez);
	sz = 1;
	if ( ez < 0 )
		sz = -1;
	fz = dz / sqrt( 1 - dz*dz );
	result["sz"] = sz;
	result["fz"] = fz;
	
	return result;
}

calc_cam_lookat( fparams, cam_origin, target )
{
	C = cam_origin;
	T = target;
	CT = T-C;
	X = VectorNormalize( VectorCross( CT, (0,0,1) ) );
	Z = VectorNormalize( VectorCross( X, CT ) );

	CTx = CT - (VectorDot( CT, Z ) * Z );
	Hx = Length( CTx );
	fx = fparams["fx"];
	sx = fparams["sx"];
	Dx = Hx*fx;
	hx = Hx * fx*fx;
	lx = Hx*fx*sqrt( 1 - (fx*fx) );
	TPx = hx*CTx +lx*X;

	CTz = CT - (VectorDot( CT, X ) * X );
	Hz = Length( CTz );
	fz = fparams["fz"];
	sz = fparams["sz"];
	Dz = Hz*fz;
	hz = Hz * fz*fz;
	lz = Hz*fz*sqrt( 1 - (fz*fz) );
	TPz = hz*CTz +lz*Z;
	
	P = T + sx*TPx + sz*TPz;
	return P;
}



debug_draw_path( path )
{
	color = (1,1,1);
	prvpoint = path[0];
	newpoint = prvpoint;
	prvIdx = 0;
	speed = 10;
	lookahead = 30;
	if (level.use_lookahead)
	{
		while ( true )
		{
			newrecord = lookahead_path( prvIdx, prvpoint, speed, lookahead, path );
			prvIdx = newrecord[0];
			newpoint = newrecord[1];
			if (distance(prvpoint, newpoint) < 0.1)
				break;
			line( prvpoint, newpoint, color, 1, true, 1 );
			prvpoint = newpoint;
			if ( prvIdx >= path.size )
				break;
		}
	}
	else
	{
		for ( i=0; i<(path.size-1); i++ )
		{
			line( path[i], path[i+1], color, 1, true, 1 );
		}
	}
}

debug_draw_obstacles( obstacles )
{
	color = (1,0.5,0);
	foreach (obstacle in obstacles)
	{
		center = obstacle["center"];
		radius = obstacle["radius"];
		Cylinder( center, center + (0,0,100), radius, color, true, 1 );
	}
}

debug_draw_aim( camera )
{
	cntsBtnDraws = 1;
	cnt = cntsBtnDraws;
	aimLen = 36;
	init_path_traversal( camera );
	camera.target_from_avatar = get_target_from_avatar( camera.targetAvatar );
	while ( !update_camera_on_path( camera ) )
	{
		target = camera.target_from_avatar;
		cam_target = calc_cam_lookat( camera.fparams, camera.origin, target );
		cnt--;
		if (cnt <= 0)
		{
			cnt = cntsBtnDraws;
			line( camera.origin, camera.origin + aimLen*AnglesToForward( camera.angles ), (1,0,0), 1, true, 1 );
			line( camera.origin, cam_target, (1,0.5,0.5), 1, true, 1 );
		}
	}
	// ensure we're clamped into a good range for all camera angles
	camera.angles = ( AngleClamp180( camera.angles[0] ), AngleClamp180( camera.angles[1] ), AngleClamp180( camera.angles[2] ) );
	camera DontInterpolate();

}

test_pathing( camera, startEnt, endEnt, obstacles )
{
	level notify( "stop_test_pathing" );
	level endon( "stop_test_pathing" );
	player_points =   GetEntArray( "player_pos", "targetname" );
	avatar = endEnt.spawned_avatar;
	
	
	CameraData = RotateAvatarTagCamera(avatar);
	tag_camera_loc_vec3 = CameraData.camera_tag_origin;
	tag_camera_angles_vec3 = CameraData.camera_tag_angles;
	
	end = tag_camera_loc_vec3;
	angles = tag_camera_angles_vec3;
	start = camera.origin;
	
	StartLookAtLoc = start + (AnglesToForward(camera.angles) * 64);
	EndLookAtLoc = end + (AnglesToForward(angles) * 64);
	
	
//	start = startEnt.camera_helper.origin;
//	startTarget = startEnt.camera_lookat.origin;
//	startDir = startTarget - start;
//	angles = VectorToAngles( startDir );
//	end = endEnt.camera_helper.origin;
	camera.origin = start;
	camera.angles = angles;
	camera.obstacles = obstacles;
	init_path_constants( camera );
	//path = find_best_cam_path( start, end, obstacles );
	build_path_info( camera, undefined, camera.origin, end, angles );
	camera.fparams = calc_f_from_avatar( avatar );
	camera.targetAvatar = avatar;
	while (true)
	{
		camera.origin = start;
		camera.angles = angles;
		line( start-(5,0,0), start+(5,0,0), (1,0,1) );
		line( start-(0,5,0), start+(0,5,0), (1,0,1) );
		line( start-(0,0,5), start+(0,0,5), (1,0,1) );
		line( end-(5,0,0), end+(5,0,0), (1,1,0) );
		line( end-(0,5,0), end+(0,5,0), (1,1,0) );
		line( end-(0,0,5), end+(0,0,5), (1,1,0) );
		line( start, StartLookAtLoc, (0,1,1) );
		line( end, EndLookAtLoc, (0,1,1) );
		debug_draw_path( camera.path );
		debug_draw_obstacles( camera.obstacles );
		debug_draw_aim( camera );
		wait 0.05;
	}
}

debug_pathing()
{
	trigger = false;
	player_points =   GetEntArray( "player_pos", "targetname" );
	foreach ( AvatarSpawn in player_points )
	{
		CameraTarget = GetEnt(AvatarSpawn.target,"targetname");
		if(CameraTarget.script_noteworthy == "camera_target")
		{
			AvatarSpawn.camera_lookat = CameraTarget;
		}
		CameraEnt = GetEnt(CameraTarget.target,"targetname");
		if(CameraEnt.script_noteworthy == "camera")
		{
			AvatarSpawn.camera_helper = CameraEnt;
			CameraEnt.camera_goal = AvatarSpawn;
		
			CameraEnt.camera_lookat = AvatarSpawn.camera_lookat;
		}
	}
	fakecamera = spawn( "script_model", (0,0,0) );
	fakecamera SetModel( "tag_player" );

	level.use_lookahead = false;	
	startIdx = 0;
	endIdx = 1;
	obstaclemask = "111111111111111111";
	radius = OBSTACLE_RADIUS;
	while (true)
	{
		if ( self FragButtonPressed() )
		{
			while ( self FragButtonPressed() )
				wait 0.05;
			endIdx++;
			if ( endIdx >= 12 )
				endIdx = 0;
			if (endIdx == startIdx)
			{
				endIdx++;
				if ( endIdx >= 12 )
					endIdx = 0;
			}
			trigger = true;
		}
		if ( self SecondaryOffhandButtonPressed() )
		{
			while ( self SecondaryOffhandButtonPressed() )
				wait 0.05;
			startIdx++;
			if ( startIdx >= 12 )
				startIdx = 0;
			if (endIdx == startIdx)
			{
				startIdx++;
				if ( startIdx >= 12 )
					startIdx = 0;
			}
			trigger = true;
		}
		if (trigger)
		{
			trigger = false;
			
			obstacles = [];
			start_point = undefined;
			end_point = undefined;
			foreach (idx, player_point in player_points)
			{
				if ( /*( idx == startIdx ) || ( idx == endIdx ) || */ (GetSubStr( obstaclemask, idx, idx+1 ) == "0" ) )
				    continue;
				obstacle = [];
				obstacle["center"] = player_point.origin;
				obstacle["radius"] = radius;
				obstacles[obstacles.size] = obstacle;
				if (player_point.script_noteworthy == ""+startIdx)
					start_point = player_point;
				if (player_point.script_noteworthy == ""+endIdx)
					end_point = player_point;
			}
			thread test_pathing( fakecamera, start_point, end_point, obstacles );
		}
		wait 0.2;
	}
}

debug_fly( camera )
{
	level.debug_fly = true;
	speed = 30;
	latspeed = 10;
	upspeed = 10;
	angles = self GetUnNormalizedCameraMovement();
	delta = self GetNormalizedMovement();
	forward = AnglesToForward( angles );
	up = AnglesToUp( angles );
	right = AnglesToRight( angles );
	deltaUp = 0;
	if ( self AdsButtonPressed() )
		deltaUp = -1;
	else if ( self AttackButtonPressed() )
		deltaUp = 1;
	if ( self SecondaryOffhandButtonPressed() )
	{
		speed = speed * 0.1;
		latspeed = latspeed * 0.1;
		upspeed = upspeed * 0.1;
	}
		
	camera.angles = angles;
	camera.origin = camera.origin + delta[0]*speed*forward + delta[1]*latspeed*right + deltaUp*upspeed*up;
}



vlobby_vegnette(opacity, material)
{
	if ( !isDefined( self.vegnette ) )
	{
        self.vegnette = newClientHudElem( self );
        self.vegnette.x = 0;
        self.vegnette.y = 0;
        self.vegnette setshader( material, 640, 480 );
        self.vegnette.alignX = "left";
        self.vegnette.alignY = "top";
        self.vegnette.horzAlign = "fullscreen";
        self.vegnette.vertAlign = "fullscreen";
        self.vegnette.alpha = opacity;
	}
	
	//disable the vignette effect when opacity is set to "0" and the vegnette has already been enabled 
	if ( isDefined( self.vegnette ) && self.vegnette.alpha > 0  && opacity == 0)
	{
		self.vegnette setshader( material, 640, 480 );
		self.vegnette.alpha = 0;
	}
	
	if ( isDefined( self.vegnette ) && self.vegnette.alpha < 1  && opacity == 1)
	{
		self.vegnette setshader( material, 640, 480 );
		self.vegnette.alpha = 1;
	}
}
